KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > FixLineSyntaxState


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.editor;
21
22 import java.util.Collections JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import javax.swing.event.DocumentEvent JavaDoc;
26 import javax.swing.text.AbstractDocument JavaDoc;
27 import javax.swing.text.Element JavaDoc;
28 import javax.swing.text.Document JavaDoc;
29 import javax.swing.text.AttributeSet JavaDoc;
30 import javax.swing.text.Position JavaDoc;
31 import javax.swing.undo.AbstractUndoableEdit JavaDoc;
32 import javax.swing.undo.CannotRedoException JavaDoc;
33 import javax.swing.undo.CannotUndoException JavaDoc;
34 import javax.swing.undo.UndoableEdit JavaDoc;
35 import javax.swing.text.BadLocationException JavaDoc;
36 import javax.swing.text.Segment JavaDoc;
37 import javax.swing.text.StyleContext JavaDoc;
38 import org.openide.ErrorManager;
39
40 /**
41  * Undoable edit that fixes syntax state infos
42  * (stored at beginings of lines)
43  * after each document modification.
44  *
45  * <p>
46  * As the syntax state infos can only be fixed
47  * AFTER the line elements get fixed there are
48  * actually two instances of this class created
49  * for each document modification.
50  * One instance triggers the update during
51  * regular modification or redo
52  * (inserted after line elements undoable edit)
53  * and the other one during undo operations
54  * (inserted before line elements undoable edit).
55  *
56  * @author Miloslav Metelka
57  * @version 1.00
58  */

59
60 final class FixLineSyntaxState {
61     
62     private static final boolean debug = false;
63     
64     private final DocumentEvent JavaDoc evt;
65     
66     private int syntaxUpdateOffset;
67         
68     private List JavaDoc syntaxUpdateTokenList = Collections.EMPTY_LIST;
69     
70     
71     FixLineSyntaxState(DocumentEvent JavaDoc evt) {
72         this.evt = evt;
73     }
74     
75     final int getSyntaxUpdateOffset() {
76         return syntaxUpdateOffset;
77     }
78         
79     final List JavaDoc getSyntaxUpdateTokenList() {
80         return syntaxUpdateTokenList;
81     }
82
83
84     static void invalidateAllSyntaxStateInfos(BaseDocument doc) {
85         LineRootElement lineRoot = getLineRoot(doc);
86         int elemCount = lineRoot.getElementCount();
87         for (int i = elemCount - 1; i >= 0; i--) {
88             LineElement line = (LineElement)lineRoot.getElement(i);
89             line.clearSyntaxStateInfo();
90         }
91     }
92     
93     /** Prepare syntax scanner so that it's ready to scan from requested
94      * position.
95      * @param text text segment to be used. Method ensures it will
96      * be filled so that <CODE>text.array</CODE> contains the character data
97      * <BR><CODE>text.offset</CODE> logically points to <CODE>reqPos</CODE>
98      * <BR><CODE>text.count</CODE> equals to <CODE>reqLen</CODE>.
99      * @param syntax syntax scanner to be used
100      * @param reqPos position to which the syntax should be prepared
101      * @param reqLen length that will be scanned by the caller after the syntax
102      * is prepared. The prepareSyntax() automatically preloads this area
103      * into the given text segment.
104      * @param forceLastBuffer force the syntax to think that the scanned area is the last
105      * in the document. This is useful for forcing the syntax to process all the characters
106      * in the given area.
107      * @param forceNotLastBuffer force the syntax to think that the scanned area is NOT
108      * the last buffer in the document. This is useful when the syntax will continue
109      * scanning on another buffer.
110      */

111     static void prepareSyntax(BaseDocument doc, Segment JavaDoc text, Syntax syntax, int reqPos, int reqLen,
112     boolean forceLastBuffer, boolean forceNotLastBuffer) throws BadLocationException JavaDoc {
113
114         if (reqPos < 0 || reqLen < 0 || reqPos + reqLen > doc.getLength()) {
115             throw new BadLocationException JavaDoc("reqPos=" + reqPos // NOI18N
116
+ ", reqLen=" + reqLen + ", doc.getLength()=" + doc.getLength(), // NOI18N
117
-1 // getting rid of it
118
);
119         }
120
121         // Find line element that covers the reqPos
122
LineRootElement lineRoot = getLineRoot(doc);
123         int reqPosLineIndex = lineRoot.getElementIndex(reqPos);
124         Element JavaDoc reqPosLineElem = lineRoot.getElement(reqPosLineIndex);
125         Syntax.StateInfo stateInfo = getValidSyntaxStateInfo(doc, reqPosLineIndex);
126         int lineStartOffset = reqPosLineElem.getStartOffset();
127         int preScan = (stateInfo != null) ? stateInfo.getPreScan() : 0;
128 // if (debug) {
129
// /*DEBUG*/System.err.println("prepareSyntax(): reqPos=" + reqPos + ", reqLen=" + reqLen
130
// + ", lineIndex=" + reqPosLineIndex
131
// + ", lineStartOffset=" + lineStartOffset
132
// + ", preScan=" + preScan
133
// );
134
// }
135
if (preScan > lineStartOffset) {
136             // an error is occurring - preScan should not reach prior to document start
137
if (debug) {
138                 /*DEBUG*/System.err.println(lineInfosToString(doc));
139             }
140             preScan = lineStartOffset; // Fix the invalid preScan
141
}
142         // load syntax segment
143
int intraLineLength = reqPos - lineStartOffset;
144         doc.getText(lineStartOffset - preScan, preScan + intraLineLength + reqLen, text);
145         text.offset += preScan;
146         text.count -= preScan;
147
148         // load state into syntax scanner - will scan from mark up to reqPos
149
syntax.load(stateInfo, text.array, text.offset, intraLineLength, false, reqPos);
150 // [CAUTION] instead of false used to be forceNotLastBuffer ? false : (reqPos >= docLen)
151

152         // ignore tokens until reqPos is reached
153
while (syntax.nextToken() != null) { }
154
155         text.offset += intraLineLength;
156         text.count -= intraLineLength;
157         boolean forceLB = forceNotLastBuffer
158             ? false
159             : (forceLastBuffer || (reqPos + reqLen >= doc.getLength()));
160
161         syntax.relocate(text.array, text.offset, text.count, forceLB, reqPos + reqLen);
162     }
163
164     /**
165      * Make sure that the line element with the requested line index
166      * has the valid syntax state info and return it.
167      * <br>
168      * Method first checks whether the line elemnt for the requested line index
169      * has valid syntax state info. If not it goes back through the previous lines
170      * until it finds a line element with valid state info. After that
171      * it starts lexing and updates syntax infos in line elements until
172      * it reaches originally requested line index.
173      * <br>
174      * For the first line the <code>null</code> state info is returned.
175      */

176     private static Syntax.StateInfo getValidSyntaxStateInfo(
177     BaseDocument doc, int lineIndex) throws BadLocationException JavaDoc {
178         
179         if (lineIndex == 0) {
180             return null;
181         }
182
183         LineRootElement lineRoot = getLineRoot(doc);
184         LineElement lineElem = (LineElement)lineRoot.getElement(lineIndex);
185         Syntax.StateInfo stateInfo = lineElem.getSyntaxStateInfo();
186
187         if (lineIndex > 0 && stateInfo == null) { // need to update
188
// Find the last line with the valid state info
189
int validLineIndex = lineIndex - 1; // is >= 0
190
LineElement validLineElem = null;
191             while (validLineIndex > 0) {
192                 validLineElem = (LineElement)lineRoot.getElement(validLineIndex);
193                 stateInfo = validLineElem.getSyntaxStateInfo() ;
194                 if (stateInfo != null) {
195                     break;
196                 }
197                 validLineIndex--;
198             }
199
200             /* validLineIndex now contains index of last line
201              * that has valid syntax state info. Or it's zero (always valid).
202              * stateInfo contains state info of last valid line
203              * or undefined value if validLineIndex == 0.
204              * validLineElem contains valid line element
205              * or undefined value if validLineIndex == 0.
206              */

207
208             Segment JavaDoc text = DocumentUtilities.SEGMENT_CACHE.getSegment();
209             try {
210                 Syntax syntax = doc.getFreeSyntax();
211                 try {
212                     int lineElemOffset = lineElem.getStartOffset();
213                     int preScan = 0;
214                     int validLineOffset;
215                     if (validLineIndex > 0) {
216                         validLineOffset = validLineElem.getStartOffset();
217                         preScan = stateInfo.getPreScan();
218                     } else { // validLineIndex == 0
219
validLineOffset = 0;
220                         stateInfo = null;
221                     }
222
223                     doc.getText(validLineOffset - preScan,
224                         (lineElemOffset - validLineOffset) + preScan,
225                         text
226                     );
227
228                     text.offset += preScan;
229                     text.count -= preScan;
230                     /* text segment contains all the required data including preScan
231                      * but "officially" it points to validLineOffset offset.
232                      */

233                     
234                     syntax.load(stateInfo, text.array, text.offset,
235                         text.count, false, lineElemOffset);
236
237                     int textEndOffset = text.offset + text.count;
238                     do {
239                         validLineIndex++;
240                         validLineElem = (LineElement)lineRoot.getElement(validLineIndex);
241                         int scanLength = validLineOffset; // get orig value
242
validLineOffset = validLineElem.getStartOffset();
243                         scanLength = validLineOffset - scanLength;
244                         syntax.relocate(text.array, syntax.getOffset(),
245                             scanLength,
246                             false, validLineOffset
247                         );
248                         
249                         while (syntax.nextToken() != null) {
250                             // ignore returned tokens
251
}
252                         
253                         validLineElem.updateSyntaxStateInfo(syntax);
254                         
255                     } while (validLineIndex != lineIndex);
256                     
257                 } finally {
258                     doc.releaseSyntax(syntax);
259                 }
260             } finally {
261                 DocumentUtilities.SEGMENT_CACHE.releaseSegment(text);
262             }
263         }
264         
265         return lineElem.getSyntaxStateInfo();
266     }
267
268     void update(boolean undo) {
269         SyntaxUpdateTokens suTokens = (SyntaxUpdateTokens)evt.getDocument().getProperty(SyntaxUpdateTokens.class);
270         if (suTokens != null) {
271             suTokens.syntaxUpdateStart();
272         }
273         try {
274             // Update syntax state infos (based on updated text and line structures
275
syntaxUpdateOffset = fixSyntaxStateInfos(undo);
276         } finally {
277             if (suTokens != null) {
278                 syntaxUpdateTokenList = Collections.unmodifiableList(
279                     new ArrayList JavaDoc(suTokens.syntaxUpdateEnd()));
280             }
281         }
282     }
283
284     /**
285      * Fix state infos after insertion/removal.
286      * @param offset offset of the modification
287      * @param length length of the modification. It's lower than zero for removals.
288      * @return offset of the last line where the syntax stateinfo was modified.
289      */

290     private int fixSyntaxStateInfos(boolean undo) {
291         int offset = evt.getOffset();
292         if (offset < 0) {
293             throw new IllegalStateException JavaDoc("offset=" + offset); // NOI18N
294
}
295
296         BaseDocument doc = (BaseDocument)evt.getDocument();
297         LineRootElement lineRoot = getLineRoot(doc);
298         int lineCount = lineRoot.getElementCount();
299         DocumentEvent.ElementChange JavaDoc lineChange = evt.getChange(lineRoot);
300         int lineIndex;
301         if (lineChange != null) {
302             lineIndex = lineChange.getIndex();
303         } else { // no change in line elements
304
lineIndex = lineRoot.getElementIndex(offset);
305         }
306         
307         // As done in AbstractDocument.ElementEdit.undo()/redo()
308
// the childrenAdded and childrenRemoved fields
309
// are switched during undo()/redo() so in fact
310
// the added line elements should be always found
311
// by getChildrenAdded().
312
int addedLinesCount = (lineChange != null)
313             ? lineChange.getChildrenAdded().length
314             : 0;
315         
316         int maybeMatchLineIndex = lineIndex + addedLinesCount + 1;
317         if (lineIndex > 0) {
318             lineIndex--; // Move to previous line
319
}
320         if (lineIndex + 1 == lineCount) { // on last line -> no fixing
321
return doc.getLength();
322         }
323
324         LineElement lineElem = (LineElement)lineRoot.getElement(lineIndex);
325         
326         Segment JavaDoc text = DocumentUtilities.SEGMENT_CACHE.getSegment();
327         try {
328             Syntax.StateInfo stateInfo = getValidSyntaxStateInfo(doc, lineIndex);
329             int lineStartOffset = lineElem.getStartOffset();
330             int preScan = (stateInfo != null) ? stateInfo.getPreScan() : 0;
331             
332             if (debug) {
333                 /*DEBUG*/System.err.println("fixSyntaxStateInfos(): lineIndex=" + lineIndex
334                     + ", maybeMatch=" + maybeMatchLineIndex // NOI18N
335
+ ", lineStartOffset=" + lineStartOffset // NOI18N
336
+ ", preScan=" + preScan // NOI18N
337
+ ", addedLines=" + addedLinesCount // NOI18N
338
+ ", lineCount=" + lineCount // NOI18N
339
);
340             }
341
342             Syntax syntax = doc.getFreeSyntax();
343             try {
344                 lineIndex++; // line index now points to line that follows the modified one
345
LineElement nextLineElem = (LineElement)lineRoot.getElement(lineIndex); // should be valid
346
int nextLineStartOffset = nextLineElem.getStartOffset();
347
348                 doc.getText(lineStartOffset - preScan,
349                     (nextLineStartOffset - lineStartOffset) + preScan, text);
350
351                 text.offset += preScan;
352                 text.count -= preScan;
353
354                 syntax.load(stateInfo, text.array, text.offset, text.count,
355                     false, nextLineStartOffset);
356
357                 SyntaxUpdateTokens suTokens = (SyntaxUpdateTokens)doc.getProperty(
358                     SyntaxUpdateTokens.class);
359                 /* Fix of #39446 - slow editing of long comments
360                  * Numerous doc.getText() have to be eliminated
361                  * because they span gap in the content character buffer
362                  * so character copying is being done.
363                  * The area retrieved from the document is made wider
364                  * than necessary and doubled
365                  * with each next state info being fixed.
366                  * This ensures maximum of log2(doc.getLength())
367                  * doc.geText() operations.
368                  */

369                 int textLength = -1;
370                 int textStartOffset = -1;
371                 int textBufferStartOffset = -1;
372                 while (true) {
373                     // Go through all the found relexed tokens
374
int tbStartOffset = lineStartOffset - text.offset;
375                     TokenID tokenID = syntax.nextToken();
376                     while (tokenID != null) {
377                         if (suTokens != null) {
378                            // Report each relexed token
379
suTokens.syntaxUpdateToken(tokenID,
380                                 syntax.getTokenContextPath(),
381                                 tbStartOffset + syntax.getTokenOffset(),
382                                 syntax.getTokenLength()
383                             );
384                         }
385
386                         tokenID = syntax.nextToken();
387                     }
388
389                     stateInfo = nextLineElem.getSyntaxStateInfo(); // original state info
390
if (lineIndex >= maybeMatchLineIndex) {
391                         if (stateInfo != null
392                             && syntax.compareState(stateInfo) == Syntax.EQUAL_STATE
393                         ) {
394                             // Matched at the begining of nextLineElem
395
// therefore use nextLineStartOffset as the matching offset
396
lineStartOffset = nextLineStartOffset;
397                             if (debug) {
398                                 /*DEBUG*/System.err.println("fixSyntaxStateInfos(): MATCHED at lineIndex="
399                                     + lineIndex
400                                     + ": preScan=" + syntax.getPreScan() // NOI18N
401
+ ", return lineStartOffset=" + lineStartOffset // NOI18N
402
);
403                             }
404                             break;
405                         }
406                     }
407                     
408                     nextLineElem.updateSyntaxStateInfo(syntax);
409                     if (debug) {
410                         /*DEBUG*/System.err.println("fixSyntaxStateInfos(): Updated info at line "
411                             + lineIndex + " from " + stateInfo // NOI18N
412
+ " to " + nextLineElem.getSyntaxStateInfo()); // NOI18N
413
}
414                     
415                     lineIndex++;
416                     if (lineIndex >= lineCount) { // still not match at begining of last line
417
return doc.getLength();
418                     }
419
420                     lineElem = nextLineElem;
421                     lineStartOffset = nextLineStartOffset;
422
423                     nextLineElem = (LineElement)lineRoot.getElement(lineIndex);
424                     nextLineStartOffset = nextLineElem.getStartOffset();
425                     
426                     preScan = syntax.getPreScan();
427                     
428                     /* (testing disabled)
429                     Segment checkText = new Segment(); // Construct segment for testing
430                     doc.getText(lineStartOffset - preScan,
431                         (nextLineStartOffset - lineStartOffset) + preScan, checkText);
432                      */

433                     
434                     int requestedTextLength = (nextLineStartOffset - lineStartOffset) + preScan;
435                     // Fixed #39446 - slow editing of long comments
436
if (textLength == -1) { // not retrieved yet
437
textStartOffset = lineStartOffset - preScan;
438                         textLength = requestedTextLength;
439                         doc.getText(textStartOffset, textLength, text);
440                         textBufferStartOffset = textStartOffset - text.offset;
441                         
442                     } else { // already retrieved previously
443

444                         if (lineStartOffset - preScan < textStartOffset
445                             || nextLineStartOffset > textStartOffset + textLength
446                         ) { // outside of boundaries => another getText() must be done
447
textLength = Math.max(textLength, requestedTextLength);
448                             textLength *= 2; // double to get logarithmic number of getText() calls
449
textStartOffset = lineStartOffset - preScan;
450                             textLength = Math.min(textStartOffset + textLength,
451                                 doc.getLength()) - textStartOffset;
452                             doc.getText(textStartOffset, textLength, text);
453                             textBufferStartOffset = textStartOffset - text.offset;
454                             // text.offset OK, update text.count
455

456                         } else { // the current text segment contains enough data
457
text.offset = lineStartOffset - preScan - textBufferStartOffset;
458                         }
459                         text.count = requestedTextLength;
460                     }
461                     
462                     
463                     /* (testing disabled)
464                     // Verify that the characters are the same in both segments
465                     if (checkText.count != text.count) {
466                         throw new IllegalStateException();
467                     }
468                     for (int i = 0; i < checkText.count; i++) {
469                         if (checkText.array[checkText.offset + i] != text.array[text.offset + i]) {
470                             throw new IllegalStateException();
471                         }
472                     }
473                     */

474
475                     text.offset += preScan;
476                     text.count -= preScan;
477                     
478                     syntax.relocate(text.array, text.offset, text.count,
479                         false, nextLineStartOffset);
480                 }
481                 
482                 return lineStartOffset;
483                 
484             } finally {
485                 doc.releaseSyntax(syntax);
486
487                 // The consistency check can fail although the actual state
488
// is legal (there are some null states) - see #47484
489
//checkConsistency(doc);
490
}
491         } catch (BadLocationException JavaDoc e) {
492             throw new IllegalStateException JavaDoc(e);
493         } finally {
494             DocumentUtilities.SEGMENT_CACHE.releaseSegment(text);
495         }
496     }
497
498     /**
499      * @param offset to be examined.
500      * @return offset that will be high enough to ensure that the given offset
501      * will be covered by token that can be returned from the syntax.nextToken()
502      * assuming that the syntax will be prepared with the returned token.
503      * <BR>It's not guaranteed how much bigger the returned offset will be.
504      */

505     static int getTokenSafeOffset(BaseDocument doc, int offset) {
506         if (offset == 0) { // no valid state-info at offset 0
507
return offset;
508         }
509
510         try {
511             LineRootElement lineRoot = getLineRoot(doc);
512             int lineIndex = lineRoot.getElementIndex(offset);
513             Element JavaDoc lineElem = lineRoot.getElement(lineIndex);
514             int lineStartOffset = lineElem.getStartOffset();
515             Syntax.StateInfo stateInfo = getValidSyntaxStateInfo(doc, lineIndex);
516             if (offset == lineStartOffset && stateInfo.getPreScan() == 0) {
517                 // can be done with the given offset
518
return offset;
519             }
520
521             // go to next line and maybe further for tokens
522
// crossing several lines
523
int lineCount = lineRoot.getElementCount();
524             while (++lineIndex < lineCount) {
525                 lineElem = lineRoot.getElement(lineIndex);
526                 stateInfo = getValidSyntaxStateInfo(doc, lineIndex);
527                 lineStartOffset = lineElem.getStartOffset();
528                 if (lineStartOffset - stateInfo.getPreScan() >= offset) {
529                     return lineStartOffset;
530                 }
531             }
532         } catch (BadLocationException JavaDoc e) {
533             throw new IllegalStateException JavaDoc(e.toString());
534         }
535
536         return doc.getLength();
537     }
538     
539     private static LineRootElement getLineRoot(Document JavaDoc doc) {
540         return (LineRootElement)doc.getDefaultRootElement();
541     }
542
543     private static void checkConsistency(Document JavaDoc doc) {
544         // Check whether all syntax state infos (except for the first line) are non-null
545
LineRootElement lineRoot = getLineRoot(doc);
546         int lineCount = lineRoot.getElementCount();
547         for (int i = 1; i < lineCount; i++) { // skip the very first line
548
LineElement elem = (LineElement)lineRoot.getElement(i);
549             assert (elem.getSyntaxStateInfo() != null) : "Syntax state null at line " + i + " of " + lineCount; // NOI18N
550
}
551     }
552     
553     public static String JavaDoc lineInfosToString(Document JavaDoc doc) {
554         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
555         LineRootElement lineRoot = getLineRoot(doc);
556         int lineCount = lineRoot.getElementCount();
557         for (int i = 0; i < lineCount; i++) {
558             LineElement elem = (LineElement)lineRoot.getElement(i);
559             sb.append("[" + i + "]: lineStartOffset=" + elem.getStartOffset() // NOI18N
560
+ ", info: " + elem.getSyntaxStateInfo() + "\n"); // NOI18N
561
}
562         return sb.toString();
563     }
564     
565     UndoableEdit JavaDoc createBeforeLineUndo() {
566         return new BeforeLineUndo();
567     }
568     
569     UndoableEdit JavaDoc createAfterLineUndo() {
570         return new AfterLineUndo();
571     }
572
573     final class BeforeLineUndo extends AbstractUndoableEdit JavaDoc {
574         
575         FixLineSyntaxState getMaster() {
576             return FixLineSyntaxState.this;
577         }
578         
579         public void undo() throws CannotUndoException JavaDoc {
580             update(true);
581             super.undo();
582         }
583         
584     }
585     
586     final class AfterLineUndo extends AbstractUndoableEdit JavaDoc {
587         
588         public void redo() throws CannotRedoException JavaDoc {
589             update(false);
590             super.redo();
591         }
592         
593     }
594 }
595
Popular Tags