KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > editor > structure > formatting > TagBasedLexerFormatter


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.modules.editor.structure.formatting;
21
22 import java.io.IOException JavaDoc;
23 import java.io.Writer JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Arrays JavaDoc;
26 import java.util.LinkedList JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.logging.Level JavaDoc;
29 import java.util.logging.Logger JavaDoc;
30 import javax.swing.text.BadLocationException JavaDoc;
31 import javax.swing.text.JTextComponent JavaDoc;
32 import javax.swing.text.Position JavaDoc;
33 import org.netbeans.api.lexer.Token;
34 import org.netbeans.api.lexer.TokenHierarchy;
35 import org.netbeans.api.lexer.TokenId;
36 import org.netbeans.api.lexer.TokenSequence;
37 import org.netbeans.editor.BaseDocument;
38 import org.netbeans.editor.Utilities;
39 import org.netbeans.editor.ext.ExtFormatter;
40
41 /**
42  *
43  * @author Tomasz.Slota@Sun.COM
44  */

45 public abstract class TagBasedLexerFormatter extends ExtFormatter {
46     private static final Logger JavaDoc logger = Logger.getLogger(TagBasedLexerFormatter.class.getName());
47             
48     /** Creates a new instance of TagBases */
49     public TagBasedLexerFormatter(Class JavaDoc kitClass) {
50         super(kitClass);
51     }
52     
53     protected abstract boolean isClosingTag(TokenHierarchy tokenHierarchy, int tagTokenOffset);
54     protected abstract boolean isUnformattableToken(TokenHierarchy tokenHierarchy, int tagTokenOffset);
55     protected abstract boolean isUnformattableTag(String JavaDoc tag);
56     protected abstract boolean isOpeningTag(TokenHierarchy tokenHierarchy, int tagTokenOffset);
57     protected abstract String JavaDoc extractTagName(TokenHierarchy tokenHierarchy, int tokenOffset);
58     protected abstract boolean areTagNamesEqual(String JavaDoc tagName1, String JavaDoc tagName2);
59     protected abstract boolean isClosingTagRequired(BaseDocument doc, String JavaDoc tagName);
60     protected abstract int getOpeningSymbolOffset(TokenHierarchy tokenHierarchy, int tagTokenOffset);
61     protected abstract int getTagEndingAtPosition(TokenHierarchy tokenHierarchy, int position) throws BadLocationException JavaDoc;
62     protected abstract int getTagEndOffset(TokenHierarchy tokenHierarchy, int tagStartOffset);
63     
64     protected boolean isWSTag(Token tag){
65         char chars[] = tag.text().toString().toCharArray();
66         
67         for (char c : chars){
68             if (!Character.isWhitespace(c)){
69                 return false;
70             }
71         }
72         
73         return true;
74     }
75     
76     protected int getIndentForTagParameter(TokenHierarchy<?> tokenHierarchy, int tagOffset) throws BadLocationException JavaDoc{
77         BaseDocument doc = (BaseDocument) tokenHierarchy.mutableInputSource();
78         int tagStartLine = Utilities.getLineOffset(doc, tagOffset);
79         TokenSequence<? extends TokenId> tokenSequence = tokenHierarchy.tokenSequence();
80         tokenSequence.move(tagOffset);
81         Token<? extends TokenId> token;
82         int tokenOffset;
83         
84         /*
85          * Find the offset of the first attribute if it is specified on the same line as the opening of the tag
86          * e.g. <tag |attr=
87          *
88          */

89         while (tokenSequence.moveNext()) {
90             token = tokenSequence.token();
91             tokenOffset = tokenSequence.offset();
92             if (!isWSTag(token) || tagStartLine != Utilities.getLineOffset(doc, tokenOffset)) {
93                 if (!isWSTag(token) && tagStartLine == Utilities.getLineOffset(doc, tokenOffset)){
94                     return tokenOffset - Utilities.getRowIndent(doc, tokenOffset) - Utilities.getRowStart(doc, tokenOffset);
95                 }
96                 break;
97             }
98         }
99         
100         
101         return getShiftWidth(); // default;
102
}
103     
104     @Override JavaDoc public Writer JavaDoc reformat(BaseDocument doc, int startOffset, int endOffset,
105             boolean indentOnly) throws BadLocationException JavaDoc, IOException JavaDoc {
106         LinkedList JavaDoc<TagIndentationData>unprocessedOpeningTags = new LinkedList JavaDoc<TagIndentationData>();
107         List JavaDoc<TagIndentationData>matchedOpeningTags = new ArrayList JavaDoc<TagIndentationData>();
108         doc.atomicLock();
109         TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
110         
111         if (tokenHierarchy == null){
112             logger.severe("Could not retrieve TokenHierarchy for document " + doc);
113             return null;
114         }
115         
116         try{
117             int lastLine = Utilities.getLineOffset(doc, doc.getLength());
118             int firstRefBlockLine = Utilities.getLineOffset(doc, startOffset);
119             int lastRefBlockLine = Utilities.getLineOffset(doc, endOffset);
120             int firstUnformattableLine = -1;
121             
122             boolean unformattableLines[] = new boolean[lastLine + 1];
123             int indentsWithinTags[] = new int[lastLine + 1];
124             
125             TokenSequence tokenSequence = tokenHierarchy.tokenSequence();
126             tokenSequence.moveStart();
127             boolean thereAreMoreTokens = tokenSequence.moveNext();
128             
129             
130             if (tokenSequence != null){
131                 // calc line indents - pass 1
132
do{
133                     boolean isOpenTag = isOpeningTag(tokenHierarchy, tokenSequence.offset());
134                     boolean isCloseTag = isClosingTag(tokenHierarchy, tokenSequence.offset());
135                     
136                     if (isOpenTag || isCloseTag){
137                         
138                         String JavaDoc tagName = extractTagName(tokenHierarchy, tokenSequence.offset());
139                         int tagEndOffset = getTagEndOffset(tokenHierarchy, tokenSequence.offset());
140                         int lastTagLine = Utilities.getLineOffset(doc, tagEndOffset);
141                         
142                         if (isOpenTag){
143                             
144                             TagIndentationData tagData = new TagIndentationData(tagName, lastTagLine);
145                             unprocessedOpeningTags.add(tagData);
146                             
147                             // format content of a tag that spans across multiple lines
148
int firstTagLine = Utilities.getLineOffset(doc, tokenSequence.offset());
149                             
150                             if (firstTagLine < lastTagLine){ // performance!
151
int indentWithinTag = getIndentForTagParameter(tokenHierarchy, tokenSequence.offset());
152                                 
153                                 for (int i = firstTagLine + 1; i <= lastTagLine; i ++){
154                                     indentsWithinTags[i] = indentWithinTag;
155                                 }
156                                 
157                                 // if there is only the closing symbol on the last line of tag do not indent it
158
thereAreMoreTokens &= tokenSequence.moveNext();
159                                 
160                                 while (Utilities.getLineOffset(doc, tokenSequence.offset()) < lastTagLine
161                                         || isWSTag(tokenSequence.token())){
162                                     
163                                     tokenSequence.moveNext();
164                                 }
165                                 
166                                 if (tokenSequence.offset() == tagEndOffset){
167                                     indentsWithinTags[lastTagLine] = 0;
168                                 }
169                             }
170                         } else {
171                             // isCloseTag - find matching opening tag record
172
LinkedList JavaDoc<TagIndentationData>tagsToBeRemoved = new LinkedList JavaDoc<TagIndentationData>();
173                             
174                             while (!unprocessedOpeningTags.isEmpty()){
175                                 TagIndentationData processedTD = unprocessedOpeningTags.removeLast();
176                                 
177                                 if (areTagNamesEqual(tagName, processedTD.getTagName())){
178                                     processedTD.setClosedOnLine(lastTagLine);
179                                     matchedOpeningTags.add(processedTD);
180                                     
181                                     // mark all the stuff between unformattable tag as unformattable
182
if (isUnformattableTag(tagName)){
183                                         for (int i = lastTagLine - 1; i > processedTD.getLine(); i --){
184                                             unformattableLines[i] = true;
185                                         }
186                                     }
187                                     
188                                     // forgetting preceding tags permanently
189
tagsToBeRemoved.clear();
190                                     break;
191                                 } else{
192                                     tagsToBeRemoved.add(processedTD);
193                                 }
194                             }
195                             
196                             // if matching opening tag was not found on the stack put all the tags back
197
unprocessedOpeningTags.addAll(tagsToBeRemoved);
198                         }
199                     }
200                     
201                     boolean wasPreviousTokenUnformattable = isUnformattableToken(tokenHierarchy, tokenSequence.offset());
202                     
203                     if (wasPreviousTokenUnformattable && firstUnformattableLine == -1){
204                         firstUnformattableLine = Utilities.getLineOffset(doc, tokenSequence.offset());
205                     }
206                     
207                     thereAreMoreTokens &= tokenSequence.moveNext();
208                     
209                     // detect an end of unformattable block; mark it
210
if (firstUnformattableLine > -1
211                             && (!wasPreviousTokenUnformattable || !thereAreMoreTokens)){
212                         
213                         int lastUnformattableLine = thereAreMoreTokens ?
214                             Utilities.getLineOffset(doc, tokenSequence.offset() - 1) : lastLine;
215                         
216                         for (int i = firstUnformattableLine + 1; i < lastUnformattableLine; i ++){
217                             unformattableLines[i] = true;
218                         }
219                         
220                         firstUnformattableLine = -1;
221                     }
222                 }
223                 while (thereAreMoreTokens);
224             }
225             
226             // calc line indents - pass 2
227
// TODO: optimize it
228
int indentLevels[] = new int[lastLine + 1];
229             Arrays.fill(indentLevels, 0);
230             
231             for (TagIndentationData td : matchedOpeningTags){
232                 // increase indent from one line after the opening tag
233
// up to one line before the closing tag
234

235                 for (int i = td.getLine() + 1; i <= td.getClosedOnLine() - 1; i ++){
236                     indentLevels[i] ++;
237                 }
238             }
239             
240             // when reformatting only a part of file
241
// we need to take into account the local bias
242
InitialIndentData initialIndentData = new InitialIndentData(doc, indentLevels,
243                     indentsWithinTags, firstRefBlockLine, lastRefBlockLine);
244             
245             // apply line indents
246
for (int line = firstRefBlockLine; line <= lastRefBlockLine; line ++){
247                 int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
248                 
249                 if (!unformattableLines[line] && initialIndentData.isEligibleToIndent(line)){
250                     changeRowIndent(doc, lineStart, initialIndentData.getIndent(line));
251                 }
252             }
253         } finally{
254             doc.atomicUnlock();
255         }
256         
257         return null;
258     }
259     
260     protected void enterPressed(JTextComponent JavaDoc txtComponent, int dotPos) throws BadLocationException JavaDoc {
261         BaseDocument doc = Utilities.getDocument(txtComponent);
262         int lineNumber = Utilities.getLineOffset(doc, dotPos);
263         int initialIndent = getInitialIndentFromPreviousLine(doc, lineNumber);
264         int endOfPreviousLine = Utilities.getFirstNonWhiteBwd(doc, dotPos);
265         endOfPreviousLine = endOfPreviousLine == -1 ? 0 : endOfPreviousLine;
266         TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc);
267         
268         // workaround for \n passed from code completion to reformatter
269
if (lineNumber == Utilities.getLineOffset(doc, endOfPreviousLine)){
270             return;
271         }
272         
273         int openingTagOffset = getTagEndingAtPosition(tokenHierarchy, endOfPreviousLine);
274         
275         if (isOpeningTag(tokenHierarchy, openingTagOffset)){
276             int closingTagOffset = getNextClosingTagOffset(tokenHierarchy, dotPos + 1);
277             
278             if (closingTagOffset != -1){
279                 int matchingOpeningTagOffset = getMatchingOpeningTagStart(tokenHierarchy, closingTagOffset);
280                 
281                 if (openingTagOffset == matchingOpeningTagOffset){
282                     
283                     int openingTagLine = Utilities.getLineOffset(doc, openingTagOffset);
284                     int closingTagLine = Utilities.getLineOffset(doc, closingTagOffset);
285                     
286                     if (closingTagLine == Utilities.getLineOffset(doc, dotPos)){
287                         
288                         if (openingTagLine == closingTagLine - 1){
289                             /* "smart enter"
290                              * <t>|optional text</t>
291                              */

292                             Position JavaDoc closingTagPos = doc.createPosition(getOpeningSymbolOffset(tokenHierarchy, closingTagOffset));
293                             changeRowIndent(doc, dotPos, initialIndent + getShiftWidth());
294                             doc.insertString(closingTagPos.getOffset(), "\n", null); //NOI18N
295
int newCaretPos = closingTagPos.getOffset() - 1;
296                             changeRowIndent(doc, closingTagPos.getOffset() + 1, initialIndent);
297                             newCaretPos = Utilities.getRowEnd(doc, newCaretPos);
298                             txtComponent.setCaretPosition(newCaretPos);
299                         } else{
300                             /* <t>
301                              *
302                              * |</t>
303                              */

304                             changeRowIndent(doc, dotPos, initialIndent);
305                         }
306                     }
307                 }
308                 
309                 int indent = initialIndent;
310                 
311                 if (isClosingTagRequired(doc, extractTagName(tokenHierarchy, openingTagOffset))){
312                     indent += getShiftWidth();
313                 }
314                 
315                 changeRowIndent(doc, dotPos, indent);
316             }
317         } else{
318             int indent = initialIndent;
319             
320             if (isJustBeforeClosingTag(tokenHierarchy, dotPos)){
321                 indent -= getShiftWidth();
322                 indent = indent < 0 ? 0 : indent;
323             }
324             
325             // preceeding token is not opening tag, keep same indentation
326
changeRowIndent(doc, dotPos, indent);
327         }
328     }
329     
330     @Override JavaDoc public int[] getReformatBlock(JTextComponent JavaDoc target, String JavaDoc typedText) {
331         TokenHierarchy tokenHierarchy = TokenHierarchy.get(target.getDocument());
332         if (tokenHierarchy == null){
333             logger.severe("Could not retrieve TokenHierarchy for document " + target.getDocument());
334             return null;
335         }
336         char lastChar = typedText.charAt(typedText.length() - 1);
337         
338         try{
339             int dotPos = target.getCaret().getDot();
340             
341             if (lastChar == '>') {
342                 int precedingTokenOffset = getTagEndingAtPosition(tokenHierarchy, dotPos - 1);
343                 
344                 if (isClosingTag(tokenHierarchy, precedingTokenOffset)){
345                     // the user has just entered a closing tag
346
// - reformat it unless matching opening tag is on the same line
347

348                     int openingTagOffset = getMatchingOpeningTagStart(tokenHierarchy, precedingTokenOffset);
349                     
350                     if (openingTagOffset != -1){
351                         BaseDocument doc = Utilities.getDocument(target);
352                         int openingTagLine = Utilities.getLineOffset(doc, openingTagOffset);
353                         int closingTagSymbolLine = Utilities.getLineOffset(doc, dotPos);
354                         
355                         if(openingTagLine != closingTagSymbolLine){
356                             return new int[]{precedingTokenOffset, dotPos};
357                         }
358                     }
359                 }
360             }
361             
362             else if(lastChar == '\n') {
363                 // just pressed enter
364
enterPressed(target, dotPos);
365             }
366             
367         } catch (Exception JavaDoc e){
368             logger.log(Level.SEVERE, "Exception during code formatting", e); //NOI18N
369
}
370         
371         return null;
372     }
373     
374     protected int getMatchingOpeningTagStart(TokenHierarchy tokenHierarchy, int closingTagOffset){
375         TokenSequence tokenSequence = tokenHierarchy.tokenSequence();
376         tokenSequence.move(closingTagOffset);
377         
378         String JavaDoc searchedTagName = extractTagName(tokenHierarchy, closingTagOffset);
379         int balance = 0;
380         
381         while (tokenSequence.movePrevious()){
382             int currentTokenOffset = tokenSequence.offset();
383             if (areTagNamesEqual(searchedTagName, extractTagName(tokenHierarchy, currentTokenOffset))){
384                 if (isOpeningTag(tokenHierarchy, currentTokenOffset)){
385                     if (balance == 0){
386                         return currentTokenOffset;
387                     }
388                     
389                     balance --;
390                 } else if (isClosingTag(tokenHierarchy, currentTokenOffset)){
391                     balance ++;
392                 }
393             }
394         }
395         
396         return -1;
397     }
398     
399     protected int getInitialIndentFromPreviousLine(final BaseDocument doc, final int line) throws BadLocationException JavaDoc {
400         
401         // get initial indent from the previous line
402
int initialIndent = 0;
403         
404         if (line > 0){
405             int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
406             int previousNonWhiteLineEnd = Utilities.getFirstNonWhiteBwd(doc, lineStart);
407             
408             if (previousNonWhiteLineEnd > 0){
409                 initialIndent = Utilities.getRowIndent(doc, previousNonWhiteLineEnd);
410             }
411         }
412         
413         return initialIndent;
414     }
415     
416     private int getInitialIndentFromNextLine(final BaseDocument doc, final int line) throws BadLocationException JavaDoc {
417         
418         // get initial indent from the next line
419
int initialIndent = 0;
420         
421         int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
422         int lineEnd = Utilities.getRowEnd(doc, lineStart);
423         int nextNonWhiteLineStart = Utilities.getFirstNonWhiteFwd(doc, lineEnd);
424         
425         if (nextNonWhiteLineStart > 0){
426             initialIndent = Utilities.getRowIndent(doc, nextNonWhiteLineStart, true);
427         }
428         
429         return initialIndent;
430     }
431
432     protected static int getNumberOfLines(BaseDocument doc) throws BadLocationException JavaDoc{
433         return Utilities.getLineOffset(doc, doc.getLength() - 1) + 1;
434     }
435     
436     protected int getNextClosingTagOffset(TokenHierarchy tokenHierarchy, int offset) throws BadLocationException JavaDoc{
437         TokenSequence tokenSequence = tokenHierarchy.tokenSequence();
438         tokenSequence.move(offset);
439         int currentOffset;
440         
441         while (tokenSequence.moveNext()) {
442             currentOffset = tokenSequence.offset();
443             
444             if (isClosingTag(tokenHierarchy, currentOffset)){
445                 return currentOffset;
446             }
447             
448         }
449
450         return -1;
451     }
452
453     protected boolean isJustBeforeClosingTag(TokenHierarchy tokenHierarchy, int pos) throws BadLocationException JavaDoc {
454         // default, trivial implementation
455
if (isClosingTag(tokenHierarchy, pos)){
456             return true;
457         }
458         
459         return false;
460     }
461     
462     protected Token getTokenAtOffset(TokenHierarchy tokenHierarchy, int tagTokenOffset){
463         TokenSequence tokenSequence = tokenHierarchy.tokenSequence();
464         
465         if (tokenSequence != null) {
466             tokenSequence.move(tagTokenOffset);
467             if (tokenSequence.moveNext())
468                 return tokenSequence.token();
469         }
470         
471         return null;
472     }
473     
474     protected class InitialIndentData{
475         private final int indentLevelBias;
476         private final int indentBias;
477         private final int indentLevels[];
478         private final int indentsWithinTags[];
479         
480         public InitialIndentData(BaseDocument doc, int indentLevels[], int indentsWithinTags[],
481                 int firstRefBlockLine, int lastRefBlockLine) throws BadLocationException JavaDoc{
482             
483             int initialIndent = getInitialIndentFromPreviousLine(doc, firstRefBlockLine);
484             int indentLevelBiasFromTheTop = initialIndent / getShiftWidth() - (firstRefBlockLine > 0 ? indentLevels[firstRefBlockLine - 1] : 0);
485             
486             int initialIndentFromTheBottom = getInitialIndentFromNextLine(doc, lastRefBlockLine);
487             int indentLevelBiasFromTheBottom = initialIndentFromTheBottom / getShiftWidth() - (lastRefBlockLine < getNumberOfLines(doc) - 1 ? indentLevels[lastRefBlockLine + 1] : 0);
488             
489             if (indentLevelBiasFromTheBottom > indentLevelBiasFromTheTop){
490                 indentLevelBias = indentLevelBiasFromTheBottom;
491                 initialIndent = initialIndentFromTheBottom;
492             }
493             else{
494                 indentLevelBias = indentLevelBiasFromTheTop;
495             }
496             
497             indentBias = initialIndent % getShiftWidth();
498             this.indentLevels = indentLevels;
499             this.indentsWithinTags = indentsWithinTags;
500         }
501         
502         public boolean isEligibleToIndent(int line){
503             return getActualIndentLevel(line) >= 0;
504         }
505         
506         public int getIndent(int line){
507             return indentBias + indentsWithinTags[line] + getActualIndentLevel(line) * getShiftWidth();
508         }
509         
510         private int getActualIndentLevel(int line){
511             return indentLevels[line] + indentLevelBias;
512         }
513     }
514     
515     protected static class TagIndentationData{
516         private final String JavaDoc tagName;
517         private final int line;
518         private int closedOnLine;
519         
520         public TagIndentationData(String JavaDoc tagName, int line){
521             this.tagName = tagName;
522             this.line = line;
523         }
524         
525         public String JavaDoc getTagName() {
526             return tagName;
527         }
528         
529         public int getLine() {
530             return line;
531         }
532         
533         public int getClosedOnLine() {
534             return closedOnLine;
535         }
536         
537         public void setClosedOnLine(int closedOnLine) {
538             this.closedOnLine = closedOnLine;
539         }
540     }
541 }
542
Popular Tags