KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > javacore > jmiimpl > javamodel > IndentUtil


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 package org.netbeans.modules.javacore.jmiimpl.javamodel;
20
21 import org.netbeans.jmi.javamodel.Annotation;
22 import org.netbeans.jmi.javamodel.Element;
23 import org.netbeans.jmi.javamodel.EnumConstant;
24 import org.netbeans.jmi.javamodel.Expression;
25 import org.netbeans.jmi.javamodel.Feature;
26 import org.netbeans.jmi.javamodel.Field;
27 import org.netbeans.jmi.javamodel.FieldGroup;
28 import org.netbeans.jmi.javamodel.ForStatement;
29 import org.netbeans.jmi.javamodel.JavaClass;
30 import org.netbeans.jmi.javamodel.LocalVarDeclaration;
31 import org.netbeans.jmi.javamodel.Resource;
32 import org.netbeans.jmi.javamodel.Statement;
33 import org.netbeans.jmi.javamodel.StatementBlock;
34 import org.netbeans.lib.java.parser.ASTree;
35 import org.netbeans.lib.java.parser.ParserTokens;
36 import org.netbeans.lib.java.parser.Token;
37 import org.netbeans.modules.javacore.parser.MDRParser;
38
39 /**
40  * Helper method for indentation of source text.
41  *
42  * @author Pavel Flaska
43  */

44 public final class IndentUtil {
45     
46     /**
47      * Returns a copy of the parameter <tt>s</tt>, with leading white space
48      * omitted.
49      *
50      * @param s string will be copied without leading white space
51      * @return A copy of parameter <tt>s</tt> with leading white space removed,
52      * or parameter <tt>s</tt> if it has no leading white space.
53      */

54     static String JavaDoc leftTrim(String JavaDoc s) {
55         int len = s.length();
56         char[] val = s.toCharArray();
57         int pos = 0;
58     while ((pos < len) && (val[pos] <= ' '))
59         pos++;
60         return pos == 0 ? s : s.substring(pos, len);
61     }
62     
63     /**
64      * Computes original indentation of the element. The computation mechanism
65      * is based on count of space or tab characters. Method will find first
66      * non-whitespace character and then it goes back and saves the space and
67      * tab characters.
68      *
69      * @param s java element string, for which original indentation is computed
70      * @return original indentation text (spaces and/or tabs)
71      */

72     static String JavaDoc getOriginalIndentation(String JavaDoc s, int[] origPos) {
73         int len = s.length();
74         char[] val = s.toCharArray();
75         int pos = 0;
76         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
77         
78         while ((pos < len) && (val[pos] <= ' '))
79             pos++;
80         
81         while (pos > 0 && (val[--pos] == ' ' || val[pos] == '\t'))
82             if (val[pos] == ' ' || val[pos] == '\t')
83                 result.append(val[pos]);
84         origPos[0] = pos;
85         return result.reverse().toString();
86     }
87     
88     /**
89      * Takes java element string representation and tries to indent it correctly
90      * in new place. Used especially when we want to move java model element
91      * between two places. We want to preserve formatting of the element,
92      * so we cut it from original source and put to the new place. (E.g. Element
93      * inside comments should be preserved, element inside formatting too.)
94      * But we have to care about its indentation as it can be on another
95      * indent level on new place.
96      *
97      * @param element java model element
98      * @param src text representation of java model element
99      * @return indented text representation of newly indented element
100      */

101     static String JavaDoc indentExistingElement(MetadataElement element, String JavaDoc src) {
102         StringBuffer JavaDoc result = new StringBuffer JavaDoc(80);
103         if (element instanceof EnumConstant ||
104            ((element instanceof Field && element.refImmediateComposite() instanceof FieldGroup))) {
105             // we do not care about enum constant, put it back as it was
106
return src;
107         } else if (element instanceof Statement || element instanceof Feature || element instanceof FieldGroup) {
108             int[] oP = new int[1];
109             String JavaDoc origInd = getOriginalIndentation(src, oP);
110             String JavaDoc[] statementLines = src.split("\\n"); // NOI18N
111
for (int i = 0; i < statementLines.length; i++) {
112                 String JavaDoc line = statementLines[i];
113                 if (leftTrim(line).length() == 0) {
114                     result.append('\n');
115                     continue;
116                 }
117                 if (line.endsWith("\r")) { // NOI18N
118
line = line.substring(0, line.length()-1);
119                 }
120                 result.append(element.getIndentation());
121                 if (line.indexOf(origInd) > -1) {
122                     result.append(line.substring(origInd.length()));
123                 } else {
124                     result.append(line);
125                 }
126                 if (i < (statementLines.length-1))
127                     result.append('\n');
128             }
129             return result.toString();
130         }
131         // we have done no indent changes, return the original text
132
return src;
133     }
134
135     static String JavaDoc indentNewElement(MetadataElement e) {
136         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(1024);
137         String JavaDoc sourceText = e.getRawText();
138         MetadataElement parent = (MetadataElement) e.refImmediateComposite();
139         if (e instanceof StatementBlock) {
140             sb.append(e.getElementPrefix(MetadataElement.BLOCK_OPEN_CURLY));
141             sb.append(sourceText);
142         } else if (e instanceof Annotation ||
143                    e instanceof EnumConstant)
144         {
145             sb.append(sourceText);
146         } else if (e instanceof Statement ||
147                    e instanceof Feature ||
148                    e instanceof FieldGroup)
149         {
150             // for special cases like:
151
// for statements: for (int i = 0; .... )
152
// field declarations in group: int a, b = 5, ...;
153
// skip the standard formatting.
154
if ((e instanceof LocalVarDeclaration && parent instanceof ForStatement) ||
155                 (e instanceof Field && parent instanceof FieldGroup))
156             {
157                 sb.append(sourceText);
158             } else {
159                 sb.append('\n');
160                 sb.append(e.getIndentation());
161                 sb.append(sourceText);
162             }
163         } else {
164             sb.append(sourceText);
165         }
166         return sb.toString();
167     }
168     
169     /**
170      * Returns an index of the first character of the element. It takes
171      * element padding (strictly speaking padding of its first token)
172      * and tries to separate them between two neighboring elements.
173      * Consider following code:
174      * <pre>
175      * System.out.println("Nothing to do."); // log comment
176      * // this code does nothing
177      * i = i;
178      * </pre>
179      * If the method will get statement <i><tt>i = i;</tt></i> as a parameter,
180      * it returns an offset representing line separator following after
181      * <i></tt>log&nbsp;comment</tt></i> comment. Second comment is considered
182      * as a part of parameter.
183      *
184      * @param el method count start offset of this parameter
185      * @return start offset of the element
186      */

187     static int getElementStart(Element el) {
188         MetadataElement e = (MetadataElement) el;
189         // common result, takes element paddings.
190
int result = e.getStartOffset(e.getParser(), e.getASTree(), true);
191         if (e instanceof Resource) {
192             // start of source text
193
result = 0;
194             
195         } else if (e instanceof Expression) {
196             // start offset of the first token, do not count padding
197
result = e.getStartOffset(e.getParser(), e.getASTree(), false);
198             
199         } else if (e instanceof Annotation) {
200             // We do not consider leading whitespaces as a part of an annotation.
201
// All the whitespaces and new lines are part of element, which owns annotation.
202
result = e.getParser().getToken(e.getASTree().getFirstToken()).getStartOffset();
203         }
204         return result;
205     }
206     
207     /**
208      * Returns an index of the last character of the element. It takes
209      * padding of token following after the last token of the element
210      * and tries to separate them between two neighboring elements.
211      * Consider following code:
212      * <pre>
213      * System.out.println("Nothing to do."); // log comment
214      * // this code does nothing
215      * i = i;
216      * </pre>
217      * If the method will get statement
218      * <i><tt>System.out.println("Nothing&nbsp;to&nbsp;do.");</tt></i>
219      * as a parameter, it returns an offset representing position exactly
220      * before new line after comment <i><tt>// log comment</tt></i>.
221      *
222      * @param el method count end offset of this parameter
223      * @return end offset of the parameter
224      */

225     static int getElementEnd(Element el) {
226         MetadataElement e = (MetadataElement) el;
227         // common result, takes end offset of the last character of last token
228
int result = e.getEndOffset(e.getParser(), e.getASTree());
229         if (e instanceof Resource) {
230             // end offset of the whole source text
231
result = e.getParser().getSourceText().length();
232         }
233         return result;
234     }
235     
236     /**
237      * Prints to <tt>buf</tt> all whitespaces and comments connected to
238      * <tt>token</tt>. Both heading and tailing are printed.
239      *
240      * @param token whitespaces connected to token are printed
241      * @param origToken token id for obtaining original indentation
242      * @param element existing element with tree and parser
243      * @param buf buffer where data are printed
244      */

245     static final void printTokenWithAllAround(int token, int origToken, MetadataElement element, StringBuffer JavaDoc buf) {
246         assert !element.isNew() : "Cannot call it for the new element!";
247         MDRParser parser = element.getParser();
248         Token t = parser.getToken(token-1);
249         int begin = t.getEndOffset();
250         t = parser.getToken(token+1);
251         int end = t.getStartOffset();
252         String JavaDoc s = parser.getSourceText().substring(begin, end);
253         // temporary buffer, for original indentation and for the result
254
StringBuffer JavaDoc tempInd = new StringBuffer JavaDoc();
255         if (!getOriginalIndentation(origToken, element, tempInd)) {
256             buf.append(s);
257         } else {
258             buf.append(reformat(s, element.getIndentation(), tempInd.toString()));
259         }
260     }
261     
262     /**
263      * Prints tail of the element. Finds the next token after element, grabs
264      * its padding and prints all paddings to first new line character to buffer.
265      *
266      * @param element print tail of this element
267      * @param buf buffer to append tail to
268      */

269     static final void printTailGarbage(MetadataElement element, StringBuffer JavaDoc buf) {
270         assert !element.isNew() : "Cannot call it for new elements!"; // NOI18N
271
ASTree tree = element.getASTree();
272         MDRParser parser = element.getParser();
273         buf.append(getGarbage(tree.getLastToken()+1, parser, true));
274     }
275     
276     /**
277      * Prints head of the element. Finds the first token of the element, grabs
278      * its padding and prints all paddings after first new line character to buffer.
279      *
280      * @param element print tail of this element
281      * @param buf buffer to append tail to
282      */

283     static final void printHeadGarbage(MetadataElement element, StringBuffer JavaDoc buf) {
284         assert !element.isNew() : "Cannot call it for new elements!"; // NOI18N
285
ASTree tree = element.getASTree();
286         MDRParser parser = element.getParser();
287         buf.append(getGarbage(tree.getFirstToken(), parser, false));
288     }
289     
290     static String JavaDoc getGarbage(int tokenId, MDRParser p, boolean tail) {
291         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(15);
292         int startOffset = tokenId == 0? 0 : p.getToken(tokenId-1).getEndOffset(),
293             endOffset = p.getToken(tokenId).getStartOffset();
294         Token[] pad = p.getToken(tokenId).getPadding();
295         if (pad.length > 0) {
296             for (int i = 0; i < pad.length; i++) {
297                 if (pad[i].getType() == ParserTokens.EOL) {
298                     if (tail)
299                         endOffset = pad[i].getStartOffset();
300                     else
301                         startOffset = pad[i].getStartOffset();
302                     break;
303                 }
304             }
305         }
306         return p.getSourceText().substring(startOffset, endOffset);
307     }
308     
309     static String JavaDoc printTokenWithPads(int tokenId, MDRParser p) {
310         String JavaDoc startPads = getGarbage(tokenId, p, false);
311         String JavaDoc endPads = getGarbage(tokenId+1, p, true);
312         Token token = p.getToken(tokenId);
313         return startPads + p.getText(token) + endPads;
314     }
315     
316     static final String JavaDoc reformatTokenWithPads(MetadataElement element, int tokenId) {
317         MDRParser p = element.getParser();
318         String JavaDoc startPads = getGarbage(tokenId, p, false);
319         String JavaDoc endPads = getGarbage(tokenId+1, p, true);
320         return indentExistingElement(element, startPads, tokenId) + p.getText(p.getToken(tokenId)) + endPads;
321     }
322     
323     static final void reformatHeadGarbage(MetadataElement element, int tokenId, StringBuffer JavaDoc buf) {
324         MDRParser p = element.getParser();
325         String JavaDoc startPads = getGarbage(tokenId, p, false);
326         buf.append(indentExistingElement(element, startPads, tokenId));
327     }
328     
329     static void reformatHeadGarbage(MetadataElement element, StringBuffer JavaDoc buf) {
330         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(80);
331         printHeadGarbage(element, sb);
332         buf.append(indentExistingElement2(element, sb.toString()));
333     }
334     
335     // optimize
336
static String JavaDoc indentExistingElement2(MetadataElement element, String JavaDoc src) {
337         StringBuffer JavaDoc result = new StringBuffer JavaDoc(80);
338         if (element instanceof EnumConstant ||
339            ((element instanceof Field && element.refImmediateComposite() instanceof FieldGroup))) {
340             // we do not care about enum constant, put it back as it was
341
return src;
342         } else if (element instanceof Statement || element instanceof Feature || element instanceof FieldGroup) {
343             int[] oP = new int[1];
344             String JavaDoc origInd = getOriginalIndentation(src, oP);
345             String JavaDoc[] statementLines = src.split("\\n"); // NOI18N
346
for (int i = 0; i < statementLines.length; i++) {
347                 String JavaDoc line = statementLines[i];
348                 if (line.endsWith("\r")) { // NOI18N
349
line = line.substring(0, line.length()-1);
350                 }
351                 if (line.indexOf(origInd) > -1) {
352                     result.append(element.getIndentation());
353                     result.append(line.substring(origInd.length()));
354                 } else {
355                     result.append(line);
356                 }
357                 if (i < statementLines.length-1)
358                     result.append('\n');
359             }
360             return result.toString();
361         }
362         // we have done no indent changes, return the original text
363
return src;
364     }
365
366     /**
367      * Used for obtaining original indentation from element. Takes all
368      * whitespaces and paddings before the <tt>tokenId</tt>, go through
369      * them to the last new line character and puts all the whitespaces
370      * after the last new line character to <tt>buf</bb>. If there is
371      * not any new line character before <tt>tokenId</tt>, it returns
372      * false (buf is not filled), otherwise true.
373      *
374      * @param tokenId token number used for computation
375      * @param element already existing element (not newly created) with
376      * containing parser
377      * @param buf method fills this buffer with indentation
378      * @return indentation was obtained from source, parameter buf is filled
379      * with it
380      */

381     private static boolean getOriginalIndentation(int tokenId, MetadataElement element, StringBuffer JavaDoc buf) {
382         assert !element.isNew() : "Cannot call it for the new element!";
383         MDRParser p = element.getParser();
384         Token[] pad = p.getToken(tokenId).getPadding();
385         int startOffset = -1;
386         if (pad.length > 0) {
387             int endOffset = p.getToken(tokenId).getStartOffset();
388             for (int i = 0; i < pad.length; i++) {
389                 if (pad[i].getType() == ParserTokens.EOL) {
390                     startOffset = pad[i].getEndOffset();
391                 }
392             }
393             if (startOffset != -1) {
394                 buf.append(p.getSourceText().substring(startOffset, endOffset));
395                 return true;
396             }
397         }
398         return false;
399     }
400     
401     /**
402      * Method re-indents element. It takes new indentation for <tt>element</tt>,
403      * go through the source representation in <tt>src</tt> where it replaces
404      * original indentation with new one obtained from <tt>element</tt>.
405      * Original indentation is computed from <tt>tokenId</tt>
406      *
407      * @param element method calls getIndentation() on it for obtaining
408      * new indentation
409      * @param src original source, which method re-indents
410      * @param tokenId token number, original indentation is computed
411      * from this token
412      * @return re-indented element
413      */

414     static String JavaDoc indentExistingElement(MetadataElement element, String JavaDoc src, int tokenId) {
415         assert !element.isNew() : "Cannot call it for the new element!";
416         if (element instanceof EnumConstant ||
417            ((element instanceof Field && element.refImmediateComposite() instanceof FieldGroup))) {
418             // we do not care about enum constant, put it back as it was
419
return src;
420         } else if (element instanceof Statement || element instanceof Feature || element instanceof FieldGroup) {
421             // temporary buffer, for original indentation and for the result
422
StringBuffer JavaDoc tempInd = new StringBuffer JavaDoc();
423             if (!getOriginalIndentation(tokenId, element, tempInd)) {
424                 return src;
425             }
426             return reformat(src, element.getIndentation(), tempInd.toString());
427         }
428         // we have done no indent changes, return the original text
429
return src;
430     }
431     
432     private static String JavaDoc reformat(final String JavaDoc src, final String JavaDoc newInd, final String JavaDoc origInd) {
433         StringBuffer JavaDoc result = new StringBuffer JavaDoc(80);
434         String JavaDoc[] statementLines = src.split("\\n"); // NOI18N
435
// go through all the lines in src, remove the origInd
436
// from every line if present and put the new indentation
437
// there instead
438
for (int i = 0; i < statementLines.length; i++) {
439             String JavaDoc line = statementLines[i];
440             // windows specific
441
if (line.endsWith("\r")) { // NOI18N
442
line = line.substring(0, line.length()-1);
443             }
444             // remove the original indentation from line and put
445
// there new one
446
if (line.indexOf(origInd) == 0) {
447                 result.append(newInd);
448                 result.append(line.substring(origInd.length()));
449             } else {
450                 result.append(line);
451             }
452             // put the new line character after every line,
453
// do not put it after last line
454
if (i < statementLines.length-1)
455                 result.append('\n');
456         }
457         return result.toString();
458     }
459
460 }
461
Popular Tags