KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > ext > FormatWriter


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.ext;
21
22 import java.io.IOException JavaDoc;
23 import java.io.Writer JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import javax.swing.text.BadLocationException JavaDoc;
29 import javax.swing.text.Document JavaDoc;
30 import javax.swing.text.Position JavaDoc;
31 import org.netbeans.editor.BaseKit;
32 import org.netbeans.editor.BaseDocument;
33 import org.netbeans.editor.Syntax;
34 import org.netbeans.editor.SettingsNames;
35 import org.netbeans.editor.TokenID;
36 import org.netbeans.editor.TokenContextPath;
37 import org.netbeans.editor.TokenItem;
38 import org.netbeans.editor.Utilities;
39 import org.netbeans.editor.EditorDebug;
40
41 /**
42 * Formatting writter accepts the input-text, formats it
43 * and writes the output to the underlying writer.
44 * The data written to the writer are immediately splitted
45 * into token-items and the chain of token-items is created
46 * from them. Then the writer then waits for the flush() or close()
47 * method call which then makes the real formatting.
48 * The formatting is done through going through the format-layers
49 * registered in <tt>ExtFormatter</tt> and asking them for formatting.
50 * These layers go through the chain and possibly add or remove
51 * the tokens as necessary. The good thing is that the layers
52 * can ask the tokens before those written to the writer.
53 * In that case they will get the tokens from the document at point
54 * the formatting should be done. The advantage is that the chain
55 * is compact so the border between the tokens written to the writer
56 * and those that come from the document is invisible.
57 *
58 * @author Miloslav Metelka
59 * @version 1.00
60 */

61
62 public final class FormatWriter extends Writer JavaDoc {
63
64     /** Whether debug messages should be displayed */
65     public static final boolean debug
66         = Boolean.getBoolean("netbeans.debug.editor.format"); // NOI18N
67

68     /** Whether debug messages should be displayed */
69     public static final boolean debugModify
70         = Boolean.getBoolean("netbeans.debug.editor.format.modify"); // NOI18N
71

72     private static final char[] EMPTY_BUFFER = new char[0];
73
74     /** Formatter related to this format-writer */
75     private ExtFormatter formatter;
76
77     /** Document being formatted */
78     private Document JavaDoc doc;
79
80     /** Offset at which the formatting occurs */
81     private int offset;
82
83     /** Underlying writer */
84     private Writer JavaDoc underWriter;
85
86     /** Syntax scanning the characters passed to the writer. For non-BaseDocuments
87     * it also scans the characters preceding the format offset. The goal here
88     * is to maintain the formatted tokens consistent with the scanning context
89     * at the offset in the document. This is achieved differently
90     * depending on whether the document is an instance of the BaseDocument
91     * or not.
92     * If the document is an instance of the <tt>BaseDocument</tt>,
93     * the syntax is used solely for the scanning of the formatted tokens
94     * and it is first prepared to scan right after the offset.
95     * For non-BaseDocuments the syntax first scans the whole are
96     * from the begining of the document till the offset and then continues
97     * on with the formatted tokens.
98     */

99     private Syntax syntax;
100
101     /** Whether the purpose is to find an indentation
102     * instead of formatting the tokens. In this mode only the '\n' is written
103     * to the format-writer, and the layers should insert the appropriate
104     * white-space tokens before the '\n' that will form
105     * the indentation of the line.
106     */

107     private boolean indentOnly;
108
109     /** Buffer being scanned */
110     private char[] buffer;
111
112     /** Number of the valid chars in the buffer */
113     private int bufferSize;
114
115     /** Support for creating the positions. */
116     private FormatTokenPositionSupport ftps;
117
118     /** Prescan at the offset position */
119     private int offsetPreScan;
120
121     /** Whether the first flush() is being done.
122      * it must respect the offsetPreScan.
123      */

124     private boolean firstFlush;
125
126     /** Last token-item in the chain */
127     private ExtTokenItem lastToken;
128
129     /** Position where the formatting should start. */
130     private FormatTokenPosition formatStartPosition;
131
132     /** The first position that doesn't belong to the document. */
133     private FormatTokenPosition textStartPosition;
134
135     /** This flag is set automatically if the new removal or insertion
136     * into chain occurs. The formatter can use this flag to detect whether
137     * a particular format-layer changed the chain.
138     */

139     private boolean chainModified;
140
141     /** Whether the format should be restarted. */
142     private boolean restartFormat;
143
144     /** Flag that helps to avoid unnecessary formatting when
145     * calling flush() periodically without calling write()
146     */

147     private boolean lastFlush;
148
149     /** Shift resulting indentation position to which the caret is moved.
150      * By default the caret goes to the first non-whitespace character
151      * on the formatted line. If the line is empty then to the end of the
152      * indentation whitespace. This variable enables to move the resulting
153      * position either left or right.
154      */

155     private int indentShift;
156
157     /** Whether this format writer runs in the simple mode.
158      * In simple mode the input is directly written to output.
159      */

160     private boolean simple;
161     
162     /** Added to fix #5620 */
163     private boolean reformatting;
164     /** Added to fix #5620 */
165     void setReformatting(boolean reformatting) {
166         this.reformatting = reformatting;
167     }
168
169     /** The format writers should not be extended to enable
170     * operating of the layers on all the writers even for different
171     * languages.
172     * @param underWriter underlying writer
173     */

174     FormatWriter(ExtFormatter formatter, Document JavaDoc doc, int offset,
175                  Writer JavaDoc underWriter, boolean indentOnly) {
176         this.formatter = formatter;
177         this.doc = doc;
178         this.offset = offset;
179         this.underWriter = underWriter;
180         this.setIndentOnly(indentOnly);
181
182         if (debug) {
183             System.err.println("FormatWriter() created, formatter=" + formatter // NOI18N
184
+ ", document=" + doc.getClass() + ", expandTabs=" + formatter.expandTabs() // NOI18N
185
+ ", spacesPerTab=" + formatter.getSpacesPerTab() // NOI18N
186
+ ", tabSize=" + ((doc instanceof BaseDocument) // NOI18N
187
? ((BaseDocument)doc).getTabSize() : formatter.getTabSize())
188                 + ", shiftWidth=" + ((doc instanceof BaseDocument) // NOI18N
189
? ((BaseDocument)doc).getShiftWidth() : formatter.getShiftWidth())
190             );
191         }
192
193         // Return now for simple formatter
194
if (formatter.isSimple()) {
195             simple = true;
196             return;
197         }
198
199         buffer = EMPTY_BUFFER;
200         firstFlush = true;
201
202         // Hack for getting the right kit and then syntax
203
Class JavaDoc kitClass = (doc instanceof BaseDocument)
204             ? ((BaseDocument)doc).getKitClass()
205             : formatter.getKitClass();
206
207         if (kitClass != null && BaseKit.class.isAssignableFrom(kitClass)) {
208             syntax = BaseKit.getKit(kitClass).createFormatSyntax(doc);
209         } else {
210             simple = true;
211             return;
212         }
213
214         if (!formatter.acceptSyntax(syntax)) {
215             simple = true; // turn to simple format writer
216
return;
217         }
218
219         ftps = new FormatTokenPositionSupport(this);
220
221         if (doc instanceof BaseDocument) {
222             try {
223                 BaseDocument bdoc = (BaseDocument)doc;
224
225                 /* Init syntax right at the formatting offset so it will
226                  * contain the prescan characters only. The non-last-buffer
227                  * is inforced (even when at the document end) because the
228                  * text will follow the current document text.
229                  */

230                 bdoc.getSyntaxSupport().initSyntax(syntax, offset, offset, false, true);
231                 offsetPreScan = syntax.getPreScan();
232
233                 if (debug) {
234                     System.err.println("FormatWriter: preScan=" + offsetPreScan + " at offset=" + offset);
235                 }
236
237                 if (offset > 0) { // only if not formatting from the start of the document
238
ExtSyntaxSupport sup = (ExtSyntaxSupport)bdoc.getSyntaxSupport();
239                     Integer JavaDoc lines = (Integer JavaDoc)bdoc.getProperty(SettingsNames.LINE_BATCH_SIZE);
240
241                     int startOffset = Utilities.getRowStart(bdoc,
242                             Math.max(offset - offsetPreScan, 0),
243                             -Math.max(lines.intValue(), 1)
244                     );
245
246                     if (startOffset < 0) { // invalid line
247
startOffset = 0;
248                     }
249
250                     // Parse tokens till the offset
251
TokenItem ti = sup.getTokenChain(startOffset, offset);
252
253                     if (ti != null && ti.getOffset() < offset - offsetPreScan) {
254                         lastToken = new FilterDocumentItem(ti, null, false);
255
256                         if (debug) {
257                             System.err.println("FormatWriter: first doc token=" + lastToken); // NOI18N
258
}
259
260                         // Iterate through the chain till the last item
261
while (lastToken.getNext() != null
262                                 && lastToken.getNext().getOffset() < offset - offsetPreScan
263                         ) {
264                             lastToken = (ExtTokenItem)lastToken.getNext();
265
266                             if (debug) {
267                                 System.err.println("FormatWriter: doc token=" + lastToken); // NOI18N
268
}
269                         }
270
271                         // Terminate the end of chain so it doesn't try
272
// to append the next token from the document
273
((FilterDocumentItem)lastToken).terminate();
274
275                     }
276                 }
277
278             } catch (BadLocationException JavaDoc e) {
279                 Utilities.annotateLoggable(e);
280             }
281
282         } else { // non-BaseDocument
283
try {
284                 String JavaDoc text = doc.getText(0, offset);
285                 char[] buffer = text.toCharArray();
286
287                 // Force non-last buffer
288
syntax.load(null, buffer, 0, buffer.length, false, 0);
289
290                 TokenID tokenID = syntax.nextToken();
291                 while (tokenID != null) {
292                     int tokenOffset = syntax.getTokenOffset();
293                     lastToken = new FormatTokenItem(tokenID,
294                             syntax.getTokenContextPath(),
295                             tokenOffset,
296                             text.substring(tokenOffset, tokenOffset + syntax.getTokenLength()),
297                             lastToken
298                     );
299
300                     if (debug) {
301                         System.err.println("FormatWriter: non-bd token=" + lastToken);
302                     }
303
304                     ((FormatTokenItem)lastToken).markWritten();
305                     tokenID = syntax.nextToken();
306                 }
307
308                 // Assign the preScan
309
offsetPreScan = syntax.getPreScan();
310
311             } catch (BadLocationException JavaDoc e) {
312                 Utilities.annotateLoggable(e);
313             }
314         }
315
316         // Write the preScan characters
317
char[] buf = syntax.getBuffer();
318         int bufOffset = syntax.getOffset();
319
320         if (debug) {
321             System.err.println("FormatWriter: writing preScan chars='" // NOI18N
322
+ EditorDebug.debugChars(buf, bufOffset - offsetPreScan,
323                         offsetPreScan) + "'" // NOI18N
324
+ ", length=" + offsetPreScan // NOI18N
325
);
326         }
327
328         // Write the preScan chars to the buffer
329
addToBuffer(buf, bufOffset - offsetPreScan, offsetPreScan);
330     }
331
332     public final ExtFormatter getFormatter() {
333         return formatter;
334     }
335
336     /** Get the document being formatted */
337     public final Document JavaDoc getDocument() {
338         return doc;
339     }
340
341     /** Get the starting offset of the formatting */
342     public final int getOffset() {
343         return offset;
344     }
345
346     /** Whether the purpose of this writer is to find the proper indentation
347      * instead of formatting the tokens. It allows to have a modified
348      * formatting behavior for the cases when user presses Enter or a key
349      * that causes immediate reformatting of the line.
350      */

351     public final boolean isIndentOnly() {
352         return indentOnly;
353     }
354     
355     /** Sets whether the purpose of this writer is to find the proper indentation
356      * instead of formatting the tokens.
357      * @see isIndentOnly()
358      */

359     public void setIndentOnly(boolean indentOnly) {
360         this.indentOnly = indentOnly;
361     }
362
363     /** Get the first token that should be formatted.
364     * This can change as the format-layers continue to change the token-chain.
365     * If the caller calls flush(), this method will return null. After
366     * additional writing to the writer, new tokens will be added and
367     * the first one of them will become the first token to be formatted.
368     * @return the first token that should be formatted. It can be null
369     * in case some layer removes all the tokens that should be formatted.
370     * Most of the layers probably do nothing in case this value is null.
371     */

372     public FormatTokenPosition getFormatStartPosition() {
373         return formatStartPosition;
374     }
375
376     /** Get the first position that doesn't belong to the document.
377      * Initially it's the same as the <tt>getFormatStartPosition()</tt> but
378      * if there are multiple flushes performed on the writer they will differ.
379      */

380     public FormatTokenPosition getTextStartPosition() {
381         return textStartPosition;
382     }
383
384     /** Get the last token in the chain. It can be null
385     * if there are no tokens in the chain.
386     */

387     public TokenItem getLastToken() {
388         return lastToken;
389     }
390
391     /** Find the first token in the chain. It should be used only when necessary
392      * and possibly in situations when the start of the chain
393      * was already reached by other methods, because this method
394      * will extend the chain till the begining of the document.
395      * @param token token from which the search for previous tokens will
396      * start. It can be null in which case the last document token or last
397      * token are attempted instead.
398      */

399     public TokenItem findFirstToken(TokenItem token) {
400         if (token == null) {
401             // Try textStartPosition first
402
token = (textStartPosition != null)
403                 ? textStartPosition.getToken() : null;
404
405             if (token == null) {
406                 // Try starting of the formatting position next
407
token = formatStartPosition.getToken();
408                 if (token == null) {
409                     token = lastToken;
410                     if (token == null) {
411                         return null;
412                     }
413                 }
414             }
415         }
416
417         while (token.getPrevious() != null) {
418             token = token.getPrevious();
419         }
420         return token;
421     }
422
423     /** It checks whether the tested token is after some other token in the chain.
424      * @param testedToken token to test (whether it's after afterToken or not)
425      * @param afterToken token to be compared to the testedToken
426      * @return true whether the testedToken is after afterToken or not.
427      * Returns false if the token == afterToken
428      * or not or if token is before the afterToken or not.
429      */

430     public boolean isAfter(TokenItem testedToken, TokenItem afterToken) {
431         while (afterToken != null) {
432             afterToken = afterToken.getNext();
433
434             if (afterToken == testedToken) {
435                 return true;
436             }
437         }
438
439         return false;
440     }
441
442     /** Checks whether the tested position is after some other position. */
443     public boolean isAfter(FormatTokenPosition testedPosition,
444     FormatTokenPosition afterPosition) {
445         if (testedPosition.getToken() == afterPosition.getToken()) {
446             return (testedPosition.getOffset() > afterPosition.getOffset());
447
448         } else { // different tokens
449
return isAfter(testedPosition.getToken(), afterPosition.getToken());
450         }
451     }
452
453     /** Check whether the given token has empty text and if so
454     * start searching for token with non-empty text in the given
455     * direction. If there's no non-empty token in the given direction
456     * the method returns null.
457     * @param token token to start to search from. If it has zero
458     * length, the search for non-empty token is performed in the given
459     * direction.
460     */

461     public TokenItem findNonEmptyToken(TokenItem token, boolean backward) {
462         while (token != null && token.getImage().length() == 0) {
463             token = backward ? token.getPrevious() : token.getNext();
464         }
465
466         return token;
467     }
468
469     /** Check whether a new token can be inserted into the chain
470     * before the given token-item. The token
471     * can be inserted only into the tokens that come
472     * from the text that was written to the format-writer
473     * but was not yet written to the underlying writer.
474     * @param beforeToken token-item before which
475     * the new token-item is about to be inserted. It can
476     * be null to append the new token to the end of the chain.
477     */

478     public boolean canInsertToken(TokenItem beforeToken) {
479         return beforeToken== null // appending to the end
480
|| !((ExtTokenItem)beforeToken).isWritten();
481     }
482
483     /** Create a new token-item and insert it before
484     * the token-item given as parameter.
485     * The <tt>canInsertToken()</tt> should be called
486     * first to determine whether the given token can
487     * be inserted into the chain or not. The token
488     * can be inserted only into the tokens that come
489     * from the text that was written to the format-writer
490     * but was not yet written to the underlying writer.
491     * @param beforeToken token-item before which
492     * the new token-item is about to be inserted. It can
493     * be null to append the new token to the end of the chain.
494     * @param tokenID token-id of the new token-item
495     * @param tokenContextPath token-context-path of the new token-item
496     * @param tokenImage image of the new token-item
497     */

498     public TokenItem insertToken(TokenItem beforeToken,
499     TokenID tokenID, TokenContextPath tokenContextPath, String JavaDoc tokenImage) {
500         if (debugModify) {
501             System.err.println("FormatWriter.insertToken(): beforeToken=" + beforeToken // NOI18N
502
+ ", tokenID=" + tokenID + ", contextPath=" + tokenContextPath // NOI18N
503
+ ", tokenImage='" + tokenImage + "'" // NOI18N
504
);
505         }
506
507         if (!canInsertToken(beforeToken)) {
508             throw new IllegalStateException JavaDoc("Can't insert token into chain"); // NOI18N
509
}
510
511         // #5620
512
if (reformatting) {
513             try {
514                 doc.insertString(getDocOffset(beforeToken), tokenImage, null);
515             } catch (BadLocationException JavaDoc e) {
516                 e.printStackTrace();
517             }
518         }
519         
520         FormatTokenItem fti;
521         if (beforeToken != null) {
522             fti = ((FormatTokenItem)beforeToken).insertToken(tokenID,
523                     tokenContextPath, -1, tokenImage);
524
525         } else { // beforeToken is null
526
fti = new FormatTokenItem(tokenID, tokenContextPath, -1, tokenImage, lastToken);
527             lastToken = fti;
528         }
529
530         // Update token-positions
531
ftps.tokenInsert(fti);
532
533         chainModified = true;
534
535         return fti;
536     }
537
538     /** Added to fix #5620 */
539     private int getDocOffset(TokenItem token) {
540         int len = 0;
541         if (token != null) {
542             token = token.getPrevious();
543             
544         } else { // after last token
545
token = lastToken;
546         }
547
548         while (token != null) {
549             len += token.getImage().length();
550             if (token instanceof FilterDocumentItem) {
551                  return len + token.getOffset();
552             }
553             token = token.getPrevious();
554         }
555         
556         return len;
557     }
558     
559     /** Whether the token-item can be removed. It can be removed
560     * only in case it doesn't come from the document's text
561     * and it wasn't yet written to the underlying writer.
562     */

563     public boolean canRemoveToken(TokenItem token) {
564         return !((ExtTokenItem)token).isWritten();
565     }
566
567     /** Remove the token-item from the chain. It can be removed
568     * only in case it doesn't come from the document's text
569     * and it wasn't yet written to the underlying writer.
570     */

571     public void removeToken(TokenItem token) {
572         if (debugModify) {
573             System.err.println("FormatWriter.removeToken(): token=" + token); // NOI18N
574
}
575
576         if (!canRemoveToken(token)) {
577             if (true) { // !!!
578
return;
579             }
580             throw new IllegalStateException JavaDoc("Can't remove token from chain"); // NOI18N
581
}
582
583         // #5620
584
if (reformatting) {
585             try {
586                 doc.remove(getDocOffset(token), token.getImage().length());
587             } catch (BadLocationException JavaDoc e) {
588                 e.printStackTrace();
589             }
590         }
591
592         // Update token-positions
593
ftps.tokenRemove(token);
594
595         if (lastToken == token) {
596             lastToken = (ExtTokenItem)token.getPrevious();
597         }
598         ((FormatTokenItem)token).remove(); // remove self from chain
599

600         chainModified = true;
601     }
602
603     public boolean canSplitStart(TokenItem token, int startLength) {
604         return !((ExtTokenItem)token).isWritten();
605     }
606
607     /** Create the additional token from the text at the start
608      * of the given token.
609      * @param token token being split.
610      * @param startLength length of the text at the begining of the token
611      * for which the additional token will be created.
612      * @param tokenID token-id that will be assigned to the new token
613      * @param tokenContextPath token-context-path that will be assigned
614      * to the new token
615      */

616     public TokenItem splitStart(TokenItem token, int startLength,
617     TokenID newTokenID, TokenContextPath newTokenContextPath) {
618         if (!canSplitStart(token, startLength)) {
619             throw new IllegalStateException JavaDoc("Can't split the token=" + token); // NOI18N
620
}
621
622         String JavaDoc text = token.getImage();
623         if (startLength > text.length()) {
624             throw new IllegalArgumentException JavaDoc("startLength=" + startLength // NOI18N
625
+ " is greater than token length=" + text.length()); // NOI18N
626
}
627
628         String JavaDoc newText = text.substring(0, startLength);
629         ExtTokenItem newToken = (ExtTokenItem)insertToken(token,
630                 newTokenID, newTokenContextPath, newText);
631
632         // Update token-positions
633
ftps.splitStartTokenPositions(token, startLength);
634
635         remove(token, 0, startLength);
636         return newToken;
637     }
638
639     public boolean canSplitEnd(TokenItem token, int endLength) {
640         int splitOffset = token.getImage().length() - endLength;
641         return (((ExtTokenItem)token).getWrittenLength() <= splitOffset);
642     }
643
644     /** Create the additional token from the text at the end
645      * of the given token.
646      * @param token token being split.
647      * @param endLength length of the text at the end of the token
648      * for which the additional token will be created.
649      * @param tokenID token-id that will be assigned to the new token
650      * @param tokenContextPath token-context-path that will be assigned
651      * to the new token
652      */

653     public TokenItem splitEnd(TokenItem token, int endLength,
654     TokenID newTokenID, TokenContextPath newTokenContextPath) {
655         if (!canSplitEnd(token, endLength)) {
656             throw new IllegalStateException JavaDoc("Can't split the token=" + token); // NOI18N
657
}
658
659         String JavaDoc text = token.getImage();
660         if (endLength > text.length()) {
661             throw new IllegalArgumentException JavaDoc("endLength=" + endLength // NOI18N
662
+ " is greater than token length=" + text.length()); // NOI18N
663
}
664
665         String JavaDoc newText = text.substring(0, endLength);
666         ExtTokenItem newToken = (ExtTokenItem)insertToken(token.getNext(),
667                 newTokenID, newTokenContextPath, newText);
668
669         // Update token-positions
670
ftps.splitEndTokenPositions(token, endLength);
671
672         remove(token, text.length() - endLength, endLength);
673         return newToken;
674     }
675
676     /** Whether the token can be modified either by insertion or removal
677      * at the given offset.
678      */

679     public boolean canModifyToken(TokenItem token, int offset) {
680         int wrLen = ((ExtTokenItem)token).getWrittenLength();
681         return (offset >= 0 && wrLen <= offset);
682     }
683
684     /** Insert the text at the offset inside the given token.
685      * All the token-positions at and after the offset will
686      * be increased by <tt>text.length()</tt>.
687      * <tt>IllegalArgumentException</tt> is thrown if offset
688      * is wrong.
689      * @param token token in which the text is inserted.
690      * @param offset offset at which the text will be inserted.
691      * @param text text that will be inserted at the offset.
692      */

693     public void insertString(TokenItem token, int offset, String JavaDoc text) {
694         // Check debugging
695
if (debugModify) {
696             System.err.println("FormatWriter.insertString(): token=" + token // NOI18N
697
+ ", offset=" + offset + ", text='" + text + "'"); // NOI18N
698
}
699
700         // Check empty insert
701
if (text.length() == 0) {
702             return;
703         }
704
705         // Check whether modification is allowed
706
if (!canModifyToken(token, offset)) {
707             if (true) { // !!!
708
return;
709             }
710             throw new IllegalStateException JavaDoc("Can't insert into token=" + token // NOI18N
711
+ ", at offset=" + offset + ", text='" + text + "'"); // NOI18N
712
}
713         
714         // #5620
715
if (reformatting) {
716             try {
717                 doc.insertString(getDocOffset(token) + offset, text, null);
718             } catch (BadLocationException JavaDoc e) {
719                 e.printStackTrace();
720             }
721         }
722
723
724         // Update token-positions
725
ftps.tokenTextInsert(token, offset, text.length());
726
727         String JavaDoc image = token.getImage();
728         ((ExtTokenItem)token).setImage(image.substring(0, offset) + text
729                   + image.substring(offset));
730     }
731
732     /** Remove the length of the characters at the given
733      * offset inside the given token.
734      * <tt>IllegalArgumentException</tt> is thrown if offset
735      * or length are wrong.
736      * @param token token in which the text is removed.
737      * @param offset offset at which the text will be removed.
738      * @param length length of the removed text.
739      */

740     public void remove(TokenItem token, int offset, int length) {
741         // Check debugging
742
if (debugModify) {
743             String JavaDoc removedText;
744             if (offset >= 0 && length >= 0
745                 && offset + length <= token.getImage().length()
746             ) {
747                 removedText = token.getImage().substring(offset, offset + length);
748
749             } else {
750                 removedText = "<INVALID>"; // NOI18N
751
}
752
753             System.err.println("FormatWriter.remove(): token=" + token // NOI18N
754
+ ", offset=" + offset + ", length=" + length // NOI18N
755
+ "removing text='" + removedText + "'"); // NOI18N
756
}
757
758         // Check empty remove
759
if (length == 0) {
760             return;
761         }
762
763         // Check whether modification is allowed
764
if (!canModifyToken(token, offset)) {
765             if (true) { // !!!
766
return;
767             }
768             throw new IllegalStateException JavaDoc("Can't remove from token=" + token // NOI18N
769
+ ", at offset=" + offset + ", length=" + length); // NOI18N
770
}
771
772         // #5620
773
if (reformatting) {
774             try {
775                 doc.remove(getDocOffset(token) + offset, length);
776             } catch (BadLocationException JavaDoc e) {
777                 e.printStackTrace();
778             }
779         }
780
781         // Update token-positions
782
ftps.tokenTextRemove(token, offset, length);
783
784         String JavaDoc text = token.getImage();
785         ((ExtTokenItem)token).setImage(text.substring(0, offset)
786                     + text.substring(offset + length));
787     }
788
789     /** Get the token-position that corresponds to the given
790      * offset inside the given token. The returned position is persistent
791      * and if the token is removed from chain the position
792      * is assigned to the end of the previous token or to the begining
793      * of the next token if there's no previous token.
794      * @param token token in which the position is created.
795      * @param offset inside the token at which the position
796      * will be created.
797      * @param bias forward or backward bias
798      */

799     public FormatTokenPosition getPosition(TokenItem token, int offset, Position.Bias JavaDoc bias) {
800         return ftps.getTokenPosition(token, offset, bias);
801     }
802
803     /** Check whether this is the first position in the chain of tokens. */
804     public boolean isChainStartPosition(FormatTokenPosition pos) {
805         TokenItem token = pos.getToken();
806         return (pos.getOffset() == 0)
807             && ((token == null && getLastToken() == null) // no tokens
808
|| (token != null && token.getPrevious() == null));
809     }
810
811     /** Add the given chars to the current buffer of chars to format. */
812     private void addToBuffer(char[] buf, int off, int len) {
813         // If necessary increase the buffer size
814
if (len > buffer.length - bufferSize) {
815             char[] tmp = new char[len + 2 * buffer.length];
816             System.arraycopy(buffer, 0, tmp, 0, bufferSize);
817             buffer = tmp;
818         }
819
820         // Copy the characters
821
System.arraycopy(buf, off, buffer, bufferSize, len);
822         bufferSize += len;
823     }
824
825     public void write(char[] cbuf, int off, int len) throws IOException JavaDoc {
826         if (simple) {
827             underWriter.write(cbuf, off, len);
828             return;
829         }
830
831         write(cbuf, off, len, null, null);
832     }
833
834     public synchronized void write(char[] cbuf, int off, int len,
835     int[] saveOffsets, Position.Bias JavaDoc[] saveBiases) throws IOException JavaDoc {
836         if (simple) {
837             underWriter.write(cbuf, off, len);
838             return;
839         }
840
841         if (saveOffsets != null) {
842             ftps.addSaveSet(bufferSize, len, saveOffsets, saveBiases);
843         }
844
845         lastFlush = false; // signal write() was the last so flush() can be done
846

847         if (debug) {
848             System.err.println("FormatWriter.write(): '" // NOI18N
849
+ org.netbeans.editor.EditorDebug.debugChars(cbuf, off, len)
850                     + "', length=" + len + ", bufferSize=" + bufferSize); // NOI18N
851
}
852
853         // Add the chars to the buffer for formatting
854
addToBuffer(cbuf, off, len);
855     }
856
857     /** Return the flag that is set automatically if the new removal or insertion
858     * into chain occurs. The formatter can use this flag to detect whether
859     * a particular format-layer changed the chain or not.
860     */

861     public boolean isChainModified() {
862         return chainModified;
863     }
864
865     public void setChainModified(boolean chainModified) {
866         this.chainModified = chainModified;
867     }
868
869     /** Return whether the layer requested to restart the format. The formatter
870     * can use this flag to restart the formatting from the first layer.
871     */

872     public boolean isRestartFormat() {
873         return restartFormat;
874     }
875
876     public void setRestartFormat(boolean restartFormat) {
877         this.restartFormat = restartFormat;
878     }
879
880     public int getIndentShift() {
881         return indentShift;
882     }
883
884     public void setIndentShift(int indentShift) {
885         this.indentShift = indentShift;
886     }
887
888     public void flush() throws IOException JavaDoc {
889         if (debug) {
890             System.err.println("FormatWriter.flush() called"); // NOI18N
891
}
892
893         if (simple) {
894             underWriter.flush();
895             return;
896         }
897
898         if (lastFlush) { // flush already done
899
return;
900         }
901         lastFlush = true; // flush is being done
902

903         int startOffset = 0; // offset where syntax will start scanning
904
if (firstFlush) { // must respect the offsetPreScan
905
startOffset = offsetPreScan;
906         }
907
908         syntax.relocate(buffer, startOffset, bufferSize - startOffset, true, -1);
909
910         // Reset formatStartPosition so that it will get filled with new value
911
formatStartPosition = null;
912
913         TokenID tokenID = syntax.nextToken();
914         if (firstFlush) { // doing first flush
915
if (startOffset > 0) { // check whether there's a preScan
916
while(true)
917                 {
918                 String JavaDoc text = new String JavaDoc(buffer, syntax.getTokenOffset(),
919                                               syntax.getTokenLength());
920                 // add a new token-item to the chain
921
lastToken = new FormatTokenItem(tokenID,
922                         syntax.getTokenContextPath(), -1, text, lastToken);
923
924                 if (debug) {
925                     System.err.println("FormatWriter.flush(): doc&format token=" // NOI18N
926
+ lastToken);
927                 }
928
929                 // Set that it was only partially written
930
lastToken.setWrittenLength(startOffset);
931
932                 // If the start position is inside this token, assign it
933
if (text.length() > startOffset) {
934                     formatStartPosition = getPosition(lastToken, startOffset,
935                             Position.Bias.Backward);
936                 }
937
938                 tokenID = syntax.nextToken(); // get next token
939

940                 // When force last buffer is true, the XML token chain can be split
941
// into more than one token. This does not happen for Java tokens.
942
// Because of this split must all tokens which are part of preScan
943
// (means which have end position smaller than startOffset), be changed to
944
// "unmodifiable" and only the last one token will be used.
945
// see issue 12701
946
if (text.length() >= startOffset)
947                     break;
948                 else
949                 {
950                     lastToken.setWrittenLength(Integer.MAX_VALUE);
951                     startOffset -= text.length();
952                 }
953                 }
954
955             }
956         }
957
958         while (tokenID != null) {
959             String JavaDoc text = new String JavaDoc(buffer, syntax.getTokenOffset(),
960                                           syntax.getTokenLength());
961             // add a new token-item to the chain
962
lastToken = new FormatTokenItem(tokenID,
963                     syntax.getTokenContextPath(), -1, text, lastToken);
964
965             if (formatStartPosition == null) {
966                 formatStartPosition = getPosition(lastToken, 0,
967                         Position.Bias.Backward);
968             }
969
970             if (debug) {
971                 System.err.println("FormatWriter.flush(): format token=" + lastToken);
972             }
973
974             tokenID = syntax.nextToken();
975         }
976         
977         // Assign formatStartPosition even if there are no tokens
978
if (formatStartPosition == null) {
979             formatStartPosition = getPosition(null, 0, Position.Bias.Backward);
980         }
981
982         // Assign textStartPosition if this is the first flush
983
if (firstFlush) {
984             textStartPosition = formatStartPosition;
985         }
986
987         bufferSize = 0; // reset the current buffer size
988

989         if (debug) {
990             System.err.println("FormatWriter.flush(): formatting ...");
991         }
992
993         // Format the tokens
994
formatter.format(this);
995
996         // Write the output tokens to the underlying writer marking them as written
997
StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
998         ExtTokenItem token = (ExtTokenItem)formatStartPosition.getToken();
999         ExtTokenItem prevToken = null;
1000        if (token != null) {
1001            // Process the first token
1002
switch (token.getWrittenLength()) {
1003                case -1: // write whole token
1004
sb.append(token.getImage());
1005                    break;
1006
1007                case Integer.MAX_VALUE:
1008                    throw new IllegalStateException JavaDoc("Wrong formatStartPosition"); // NOI18N
1009

1010                default:
1011                    sb.append(token.getImage().substring(formatStartPosition.getOffset()));
1012                    break;
1013            }
1014
1015            token.markWritten();
1016            prevToken = token;
1017            token = (ExtTokenItem)token.getNext();
1018
1019            // Process additional tokens
1020
while (token != null) {
1021                // First mark the previous token that it can't be extended
1022
prevToken.setWrittenLength(Integer.MAX_VALUE);
1023
1024                // Write current token and mark it as written
1025
sb.append(token.getImage());
1026                token.markWritten();
1027
1028                // Goto next token
1029
prevToken = token;
1030                token = (ExtTokenItem)token.getNext();
1031            }
1032        }
1033
1034        // Write to the underlying writer
1035
if (sb.length() > 0) {
1036            char[] outBuf = new char[sb.length()];
1037            sb.getChars(0, outBuf.length, outBuf, 0);
1038
1039            if (debug) {
1040                System.err.println("FormatWriter.flush(): chars to underlying writer='" // NOI18N
1041
+ EditorDebug.debugChars(outBuf, 0, outBuf.length)
1042                        + "'"); // NOI18N
1043
}
1044
1045            underWriter.write(outBuf, 0, outBuf.length);
1046        }
1047
1048        underWriter.flush();
1049
1050        firstFlush = false; // no more first flush
1051
}
1052
1053    public void close() throws IOException JavaDoc {
1054        if (debug) {
1055            System.err.println("FormatWriter: close() called (-> flush())"); // NOI18N
1056
}
1057
1058        flush();
1059        underWriter.close();
1060    }
1061
1062    /** Check the chain whether it's OK. */
1063    public void checkChain() {
1064        // Check whether the lastToken is really last
1065
TokenItem lt = getLastToken();
1066        if (lt.getNext() != null) {
1067            throw new IllegalStateException JavaDoc("Successor of last token exists."); // NOI18N
1068
}
1069
1070        // Check whether formatStartPosition is non-null
1071
FormatTokenPosition fsp = getFormatStartPosition();
1072        if (fsp == null) {
1073            throw new IllegalStateException JavaDoc("getFormatStartPosition() returns null."); // NOI18N
1074
}
1075
1076        // Check whether formatStartPosition follows textStartPosition
1077
checkFSPFollowsTSP();
1078
1079        // !!! implement checks:
1080
// Check whether all the document tokens have written flag true
1081
// Check whether all formatted tokens are writable
1082
}
1083
1084    /** Check whether formatStartPosition follows the textStartPosition */
1085    private void checkFSPFollowsTSP() {
1086        if (!(formatStartPosition.equals(textStartPosition)
1087                || isAfter(formatStartPosition, textStartPosition)
1088        )) {
1089            throw new IllegalStateException JavaDoc(
1090                    "formatStartPosition doesn't follow textStartPosition"); // NOI18N
1091
}
1092    }
1093
1094    public String JavaDoc chainToString(TokenItem token) {
1095        return chainToString(token, 5);
1096    }
1097
1098    /** Debug the current state of the chain.
1099    * @param token mark this token as current one. It can be null.
1100    * @param maxDocumentTokens how many document tokens should be shown.
1101    */

1102    public String JavaDoc chainToString(TokenItem token, int maxDocumentTokens) {
1103        // First check the chain whether it's correct
1104
checkChain();
1105
1106        StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1107        sb.append("D - document tokens, W - written tokens, F - tokens being formatted\n"); // NOI18N
1108

1109        // Check whether format-start position follows textStartPosition
1110
checkFSPFollowsTSP();
1111
1112        TokenItem tst = getTextStartPosition().getToken();
1113        TokenItem fst = getFormatStartPosition().getToken();
1114
1115        // Goto maxDocumentTokens back from the tst
1116
TokenItem t = tst;
1117        if (t == null) {
1118            t = getLastToken();
1119        }
1120
1121        // Go back through document tokens
1122
while (t != null && t.getPrevious() != null && --maxDocumentTokens > 0) {
1123            t = t.getPrevious();
1124        }
1125
1126        // Display the document tokens
1127
while (t != tst) {
1128            sb.append((t == token) ? '>' : ' '); // NOI18N
1129
sb.append("D "); // NOI18N
1130
sb.append(t.toString());
1131            sb.append('\n'); // NOI18N
1132

1133            t = t.getNext();
1134        }
1135
1136        while (t != fst) {
1137            sb.append((t == token) ? '>' : ' ');
1138            if (t == tst) { // found last document token
1139
sb.append("D(" + getTextStartPosition().getOffset() + ')'); // NOI18N
1140
}
1141            sb.append("W "); // NOI18N
1142
sb.append(t.toString());
1143            sb.append('\n'); // NOI18N
1144

1145            t = t.getNext();
1146        }
1147
1148        sb.append((t == token) ? '>' : ' ');
1149        if (getFormatStartPosition().getOffset() > 0) {
1150            if (fst == tst) {
1151                sb.append('D');
1152
1153            } else { // means something was already formatted
1154
sb.append('W'); // NOI18N
1155
}
1156        }
1157        sb.append("F "); // NOI18N
1158
sb.append((t != null) ? t.toString() : "NULL"); // NOI18N
1159
sb.append('\n'); // NOI18N
1160

1161        if (t != null) {
1162            t = t.getNext();
1163        }
1164
1165        while (t != null) {
1166            sb.append((t == token) ? '>' : ' '); // NOI18N
1167
sb.append("F "); // NOI18N
1168
sb.append(t.toString());
1169            sb.append('\n'); // NOI18N
1170

1171            t = t.getNext();
1172        }
1173
1174        return sb.toString();
1175    }
1176
1177    /** Token-item created for the tokens that come from the text
1178    * written to the writer.
1179    */

1180    static class FormatTokenItem extends TokenItem.AbstractItem
1181    implements ExtTokenItem {
1182
1183        /** How big part of the token was already written
1184         * to the underlying writer. -1 means nothing was written yet,
1185         * Integer.MAX_VALUE means everything was written and the token
1186         * cannot be extended and some other value means how big part
1187         * was already written.
1188         */

1189        int writtenLength = -1;
1190
1191        /** Next token in chain */
1192        TokenItem next;
1193
1194        /** Previous token in chain */
1195        TokenItem previous;
1196
1197        /** Image of the token. It's changeable. */
1198        String JavaDoc image;
1199
1200        /** Save offset used to give the relative position
1201         * to the start of the current formatting. It's used
1202         * by the FormatTokenPositionSupport.
1203         */

1204        int saveOffset;
1205
1206        FormatTokenItem(TokenID tokenID, TokenContextPath tokenContextPath,
1207        int offset, String JavaDoc image, TokenItem previous) {
1208            super(tokenID, tokenContextPath, offset, image);
1209            this.image = image;
1210            this.previous = previous;
1211            if (previous instanceof ExtTokenItem) {
1212                ((ExtTokenItem)previous).setNext(this);
1213            }
1214        }
1215
1216        public TokenItem getNext() {
1217            return next;
1218        }
1219
1220        public TokenItem getPrevious() {
1221            return previous;
1222        }
1223
1224        public void setNext(TokenItem next) {
1225            this.next = next;
1226        }
1227
1228        public void setPrevious(TokenItem previous) {
1229            this.previous = previous;
1230        }
1231
1232        public boolean isWritten() {
1233            return (writtenLength >= 0);
1234        }
1235
1236        public void markWritten() {
1237            if (writtenLength == Integer.MAX_VALUE) {
1238                throw new IllegalStateException JavaDoc("Already marked unextendable."); // NOI18N
1239
}
1240
1241            writtenLength = getImage().length();
1242        }
1243
1244        public int getWrittenLength() {
1245            return writtenLength;
1246        }
1247
1248        public void setWrittenLength(int writtenLength) {
1249            if (writtenLength <= this.writtenLength) {
1250                throw new IllegalArgumentException JavaDoc(
1251                        "this.writtenLength=" + this.writtenLength // NOI18N
1252
+ " < writtenLength=" + writtenLength); // NOI18N
1253
}
1254
1255            this.writtenLength = writtenLength;
1256        }
1257
1258        public String JavaDoc getImage() {
1259            return image;
1260        }
1261
1262        public void setImage(String JavaDoc image) {
1263            this.image = image;
1264        }
1265
1266        FormatTokenItem insertToken(TokenID tokenID, TokenContextPath tokenContextPath,
1267        int offset, String JavaDoc image) {
1268            FormatTokenItem fti = new FormatTokenItem(tokenID, tokenContextPath,
1269                    offset, image, previous);
1270            fti.next = this;
1271            this.previous = fti;
1272            return fti;
1273        }
1274
1275        void remove() {
1276            if (previous instanceof ExtTokenItem) {
1277                ((ExtTokenItem)this.previous).setNext(next);
1278            }
1279            if (next instanceof ExtTokenItem) {
1280                ((ExtTokenItem)this.next).setPrevious(previous);
1281            }
1282        }
1283
1284        int getSaveOffset() {
1285            return saveOffset;
1286        }
1287
1288        void setSaveOffset(int saveOffset) {
1289            this.saveOffset = saveOffset;
1290        }
1291
1292    }
1293
1294    /** This token item wraps every token-item that comes
1295    * from the syntax-support.
1296    */

1297    static class FilterDocumentItem extends TokenItem.FilterItem
1298    implements ExtTokenItem {
1299
1300        private static final FilterDocumentItem NULL_ITEM
1301            = new FilterDocumentItem(null, null, false);
1302
1303        private TokenItem previous;
1304
1305        private TokenItem next;
1306
1307        FilterDocumentItem(TokenItem delegate, FilterDocumentItem neighbour, boolean isNeighbourPrevious) {
1308            super(delegate);
1309            if (neighbour != null) {
1310                if (isNeighbourPrevious) {
1311                    previous = neighbour;
1312
1313                } else { // neighbour is next
1314
next = neighbour;
1315                }
1316            }
1317        }
1318
1319        public TokenItem getNext() {
1320            if (next == null) {
1321                TokenItem ti = super.getNext();
1322                if (ti != null) {
1323                    next = new FilterDocumentItem(ti, this, true);
1324                }
1325            }
1326
1327            return (next != NULL_ITEM) ? next : null;
1328        }
1329
1330        public void setNext(TokenItem next) {
1331            this.next = next;
1332        }
1333
1334        /** Change the next item to the NULL one. */
1335        public void terminate() {
1336            setNext(NULL_ITEM);
1337        }
1338
1339        public void setPrevious(TokenItem previous) {
1340            this.previous = previous;
1341        }
1342
1343        public boolean isWritten() {
1344            return true;
1345        }
1346
1347        public void markWritten() {
1348        }
1349
1350        public int getWrittenLength() {
1351            return Integer.MAX_VALUE;
1352        }
1353
1354        public void setWrittenLength(int writtenLength) {
1355            if (writtenLength != Integer.MAX_VALUE) {
1356                throw new IllegalArgumentException JavaDoc("Wrong writtenLength=" // NOI18N
1357
+ writtenLength);
1358            }
1359        }
1360
1361        public void setImage(String JavaDoc image) {
1362            throw new IllegalStateException JavaDoc("Cannot set image of the document-token."); // NOI18N
1363
}
1364
1365        public TokenItem getPrevious() {
1366            if (previous == null) {
1367                TokenItem ti = super.getPrevious();
1368                if (ti != null) {
1369                    previous = new FilterDocumentItem(ti, this, false);
1370                }
1371            }
1372
1373            return previous;
1374        }
1375
1376    }
1377
1378    interface ExtTokenItem extends TokenItem {
1379
1380        /** Set the next item */
1381        public void setNext(TokenItem next);
1382
1383        /** Set the previous item */
1384        public void setPrevious(TokenItem previous);
1385
1386        /** Was this item already flushed to the underlying writer
1387        * or does it belong to the document?
1388        */

1389        public boolean isWritten();
1390
1391        /** Set the flag marking the item as written to the underlying writer. */
1392        public void markWritten();
1393
1394        /** Get the length that was written to the output writer. It can be used
1395         * when determining whether there can be insert of the text made
1396         * at the particular offset.
1397         * @return -1 to signal that token wasn't written at all.
1398         * Or Integer.MAX_VALUE to signal that the token was written and cannot
1399         * be extended.
1400         * Or something else that means how long part of the token
1401         * was already written to the file.
1402         */

1403        public int getWrittenLength();
1404
1405        /** Set the length of the written part of the token. */
1406        public void setWrittenLength(int writtenLength);
1407
1408        /** Set the image of the token to the specified string. */
1409        public void setImage(String JavaDoc image);
1410
1411    }
1412
1413
1414}
1415
Popular Tags