KickJava   Java API By Example, From Geeks To Geeks.

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


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 javax.swing.text.BadLocationException JavaDoc;
29 import javax.swing.text.JTextComponent JavaDoc;
30 import javax.swing.text.Position JavaDoc;
31 import org.netbeans.editor.BaseDocument;
32 import org.netbeans.editor.TokenItem;
33 import org.netbeans.editor.Utilities;
34 import org.netbeans.editor.ext.ExtFormatter;
35 import org.netbeans.editor.ext.ExtSyntaxSupport;
36 import org.openide.ErrorManager;
37
38 /**
39  *
40  * @author Tomasz.Slota@Sun.COM
41  */

42 @Deprecated JavaDoc // use TagBasedLexerFormatter instead
43
public abstract class TagBasedFormatter extends ExtFormatter {
44     
45     /** Creates a new instance of TagBases */
46     public TagBasedFormatter(Class JavaDoc kitClass) {
47         super(kitClass);
48     }
49     
50     protected abstract ExtSyntaxSupport getSyntaxSupport(BaseDocument doc);
51     protected abstract boolean isClosingTag(TokenItem token);
52     protected abstract boolean isUnformattableToken(TokenItem token);
53     protected abstract boolean isUnformattableTag(String JavaDoc tag);
54     protected abstract boolean isOpeningTag(TokenItem token);
55     protected abstract String JavaDoc extractTagName(TokenItem tknTag);
56     protected abstract boolean areTagNamesEqual(String JavaDoc tagName1, String JavaDoc tagName2);
57     protected abstract boolean isClosingTagRequired(BaseDocument doc, String JavaDoc tagName);
58     protected abstract int getOpeningSymbolOffset(TokenItem tknTag);
59     protected abstract TokenItem getTagTokenEndingAtPosition(BaseDocument doc, int position) throws BadLocationException JavaDoc;
60     protected abstract int getTagEndOffset(TokenItem token);
61     
62     protected Writer JavaDoc extFormatterReformat(final BaseDocument doc, final int startOffset, final int endOffset,
63             final boolean indentOnly) throws BadLocationException JavaDoc, IOException JavaDoc {
64         return super.reformat(doc, startOffset, endOffset, indentOnly);
65     }
66     
67     protected boolean isWSTag(TokenItem tag){
68         char chars[] = tag.getImage().toCharArray();
69         
70         for (char c : chars){
71             if (!Character.isWhitespace(c)){
72                 return false;
73             }
74         }
75         
76         return true;
77     }
78     
79     protected int getIndentForTagParameter(BaseDocument doc, TokenItem tag) throws BadLocationException JavaDoc{
80         int tagStartLine = Utilities.getLineOffset(doc, tag.getOffset());
81         TokenItem currentToken = tag.getNext();
82         
83         /*
84          * Find the offset of the first attribute if it is specified on the same line as the opening of the tag
85          * e.g. <tag |attr=
86          *
87          */

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

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

283                             Position JavaDoc closingTagPos = doc.createPosition(getOpeningSymbolOffset(tknClosingTag));
284                             changeRowIndent(doc, dotPos, initialIndent + getShiftWidth());
285                             doc.insertString(closingTagPos.getOffset(), "\n", null); //NOI18N
286
int newCaretPos = closingTagPos.getOffset() - 1;
287                             changeRowIndent(doc, closingTagPos.getOffset() + 1, initialIndent);
288                             newCaretPos = Utilities.getRowEnd(doc, newCaretPos);
289                             txtComponent.setCaretPosition(newCaretPos);
290                         } else{
291                             /* <t>
292                              *
293                              * |</t>
294                              */

295                             changeRowIndent(doc, dotPos, initialIndent);
296                         }
297                     }
298                 }
299                 
300                 int indent = initialIndent;
301                 
302                 if (isClosingTagRequired(doc, extractTagName(tknOpeningTag))){
303                     indent += getShiftWidth();
304                 }
305                 
306                 changeRowIndent(doc, dotPos, indent);
307             }
308         } else{
309             int indent = initialIndent;
310             
311             if (isJustBeforeClosingTag(doc, dotPos)){
312                 indent -= getShiftWidth();
313                 indent = indent < 0 ? 0 : indent;
314             }
315             
316             // preceeding token is not opening tag, keep same indentation
317
changeRowIndent(doc, dotPos, indent);
318         }
319     }
320     
321     @Override JavaDoc public int[] getReformatBlock(JTextComponent JavaDoc target, String JavaDoc typedText) {
322         BaseDocument doc = Utilities.getDocument(target);
323         
324         if (!hasValidSyntaxSupport(doc)){
325             return null;
326         }
327         
328         char lastChar = typedText.charAt(typedText.length() - 1);
329         
330         try{
331             int dotPos = target.getCaret().getDot();
332             
333             if (lastChar == '>') {
334                 TokenItem tknPrecedingToken = getTagTokenEndingAtPosition(doc, dotPos - 1);
335                 
336                 if (isClosingTag(tknPrecedingToken)){
337                     // the user has just entered a closing tag
338
// - reformat it unless matching opening tag is on the same line
339

340                     TokenItem tknOpeningTag = getMatchingOpeningTag(tknPrecedingToken);
341                     
342                     if (tknOpeningTag != null){
343                         int openingTagLine = Utilities.getLineOffset(doc, tknOpeningTag.getOffset());
344                         int closingTagSymbolLine = Utilities.getLineOffset(doc, dotPos);
345                         
346                         if(openingTagLine != closingTagSymbolLine){
347                             return new int[]{tknPrecedingToken.getOffset(), dotPos};
348                         }
349                     }
350                 }
351             }
352             
353             else if(lastChar == '\n') {
354                 // just pressed enter
355
enterPressed(target, dotPos);
356             }
357             
358         } catch (Exception JavaDoc e){
359             ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
360         }
361         
362         return null;
363     }
364     
365     protected TokenItem getMatchingOpeningTag(TokenItem tknClosingTag){
366         String JavaDoc searchedTagName = extractTagName(tknClosingTag);
367         TokenItem token = tknClosingTag.getPrevious();
368         int balance = 0;
369         
370         while (token != null){
371             if (areTagNamesEqual(searchedTagName, extractTagName(token))){
372                 if (isOpeningTag(token)){
373                     if (balance == 0){
374                         return token;
375                     }
376                     
377                     balance --;
378                 } else if (isClosingTag(token)){
379                     balance ++;
380                 }
381             }
382             
383             token = token.getPrevious();
384         }
385         
386         return null;
387     }
388     
389     protected int getInitialIndentFromPreviousLine(final BaseDocument doc, final int line) throws BadLocationException JavaDoc {
390         
391         // get initial indent from the previous line
392
int initialIndent = 0;
393         
394         if (line > 0){
395             int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
396             int previousNonWhiteLineEnd = Utilities.getFirstNonWhiteBwd(doc, lineStart);
397             
398             if (previousNonWhiteLineEnd > 0){
399                 initialIndent = Utilities.getRowIndent(doc, previousNonWhiteLineEnd);
400             }
401         }
402         
403         return initialIndent;
404     }
405     
406     private int getInitialIndentFromNextLine(final BaseDocument doc, final int line) throws BadLocationException JavaDoc {
407         
408         // get initial indent from the next line
409
int initialIndent = 0;
410         
411         int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
412         int lineEnd = Utilities.getRowEnd(doc, lineStart);
413         int nextNonWhiteLineStart = Utilities.getFirstNonWhiteFwd(doc, lineEnd);
414         
415         if (nextNonWhiteLineStart > 0){
416             initialIndent = Utilities.getRowIndent(doc, nextNonWhiteLineStart, true);
417         }
418         
419         return initialIndent;
420     }
421     
422     private boolean hasValidSyntaxSupport(BaseDocument doc){
423         ExtSyntaxSupport sup = getSyntaxSupport(doc);
424         
425         if (sup == null){
426             ErrorManager.getDefault().log(ErrorManager.WARNING,
427                     "TagBasedFormatter: failed to retrieve SyntaxSupport for document;" + //NOI18N
428
" probably attempt to use incompatible indentation engine"); //NOI18N
429

430             return false;
431         }
432         
433         return true;
434     }
435
436     protected static int getNumberOfLines(BaseDocument doc) throws BadLocationException JavaDoc{
437         return Utilities.getLineOffset(doc, doc.getLength() - 1) + 1;
438     }
439     
440     protected TokenItem getNextClosingTag(BaseDocument doc, int offset) throws BadLocationException JavaDoc{
441         ExtSyntaxSupport sup = getSyntaxSupport(doc);
442         TokenItem token = sup.getTokenChain(offset, offset + 1);
443         
444         while (token != null){
445             if (isClosingTag(token)){
446                 return token;
447             }
448             
449             token = token.getNext();
450         }
451         
452         return null;
453     }
454
455     protected boolean isJustBeforeClosingTag(BaseDocument doc, int pos) throws BadLocationException JavaDoc {
456         ExtSyntaxSupport sup = getSyntaxSupport(doc);
457         TokenItem tknTag = sup.getTokenChain(pos, pos + 1);
458         
459         if (isClosingTag(tknTag)){
460             return true;
461         }
462         
463         return false;
464     }
465     
466     protected class InitialIndentData{
467         private final int indentLevelBias;
468         private final int indentBias;
469         private final int indentLevels[];
470         private final int indentsWithinTags[];
471         
472         public InitialIndentData(BaseDocument doc, int indentLevels[], int indentsWithinTags[],
473                 int firstRefBlockLine, int lastRefBlockLine) throws BadLocationException JavaDoc{
474             
475             int initialIndent = getInitialIndentFromPreviousLine(doc, firstRefBlockLine);
476             int indentLevelBiasFromTheTop = initialIndent / getShiftWidth() - (firstRefBlockLine > 0 ? indentLevels[firstRefBlockLine - 1] : 0);
477             
478             int initialIndentFromTheBottom = getInitialIndentFromNextLine(doc, lastRefBlockLine);
479             int indentLevelBiasFromTheBottom = initialIndentFromTheBottom / getShiftWidth() - (lastRefBlockLine < getNumberOfLines(doc) - 1 ? indentLevels[lastRefBlockLine + 1] : 0);
480             
481             if (indentLevelBiasFromTheBottom > indentLevelBiasFromTheTop){
482                 indentLevelBias = indentLevelBiasFromTheBottom;
483                 initialIndent = initialIndentFromTheBottom;
484             }
485             else{
486                 indentLevelBias = indentLevelBiasFromTheTop;
487             }
488             
489             indentBias = initialIndent % getShiftWidth();
490             this.indentLevels = indentLevels;
491             this.indentsWithinTags = indentsWithinTags;
492         }
493         
494         public boolean isEligibleToIndent(int line){
495             return getActualIndentLevel(line) >= 0;
496         }
497         
498         public int getIndent(int line){
499             return indentBias + indentsWithinTags[line] + getActualIndentLevel(line) * getShiftWidth();
500         }
501         
502         private int getActualIndentLevel(int line){
503             return indentLevels[line] + indentLevelBias;
504         }
505     }
506     
507     protected static class TagIndentationData{
508         private final String JavaDoc tagName;
509         private final int line;
510         private int closedOnLine;
511         
512         public TagIndentationData(String JavaDoc tagName, int line){
513             this.tagName = tagName;
514             this.line = line;
515         }
516         
517         public String JavaDoc getTagName() {
518             return tagName;
519         }
520         
521         public int getLine() {
522             return line;
523         }
524         
525         public int getClosedOnLine() {
526             return closedOnLine;
527         }
528         
529         public void setClosedOnLine(int closedOnLine) {
530             this.closedOnLine = closedOnLine;
531         }
532     }
533 }
534
Popular Tags