KickJava   Java API By Example, From Geeks To Geeks.

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


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

19
20 package org.netbeans.editor;
21
22 import java.util.Map JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.Writer JavaDoc;
27 import java.io.CharArrayWriter JavaDoc;
28 import javax.swing.text.Document JavaDoc;
29 import javax.swing.text.JTextComponent JavaDoc;
30 import javax.swing.text.BadLocationException JavaDoc;
31 import org.netbeans.lib.editor.util.CharSequenceUtilities;
32 import org.netbeans.lib.editor.util.swing.DocumentUtilities;
33
34 /**
35 * Various services related to indentation and text formatting
36 * are located here. Each kit can have different formatter
37 * so the first action should be getting the right formatter
38 * for the given kit by calling Formatter.getFormatter(kitClass).
39 *
40 * @author Miloslav Metelka
41 * @version 1.00
42 */

43
44 public class Formatter implements SettingsChangeListener {
45
46     private static Map JavaDoc kitClass2Formatter = new HashMap JavaDoc();
47
48     /** Get the formatter for the given kit-class */
49     public static synchronized Formatter getFormatter(Class JavaDoc kitClass) {
50         Formatter f = (Formatter)kitClass2Formatter.get(kitClass);
51         if (f == null) {
52             f = BaseKit.getKit(kitClass).createFormatter();
53             kitClass2Formatter.put(kitClass, f);
54         }
55         return f;
56     }
57     
58     /** Set the formatter for the given kit-class.
59      * @param kitClass class of the kit for which the formatter
60      * is being assigned.
61      * @param formatter new formatter for the given kit
62      */

63     public static synchronized void setFormatter(Class JavaDoc kitClass, Formatter formatter) {
64         kitClass2Formatter.put(kitClass, formatter);
65     }
66
67
68     /** Maximum tab size for which the indent strings will be cached. */
69     private static final int ISC_MAX_TAB_SIZE = 16;
70     
71     /** Cache the indentation strings up to this size */
72     private static final int ISC_MAX_INDENT_SIZE = 32;
73     
74     /** Cache holding the indentation strings for various tab-sizes. */
75     private static final String JavaDoc[][] indentStringCache
76         = new String JavaDoc[ISC_MAX_TAB_SIZE][];
77
78
79     private final Class JavaDoc kitClass;
80
81     /** Whether values were already inited from the cache */
82     private boolean inited;
83
84     private int tabSize;
85
86     private boolean customTabSize;
87
88     private Integer JavaDoc shiftWidth;
89
90     private boolean customShiftWidth;
91
92     private boolean expandTabs;
93
94     private boolean customExpandTabs;
95
96     private int spacesPerTab;
97
98     private boolean customSpacesPerTab;
99
100     /** Construct new formatter.
101     * @param kitClass the class of the kit for which this formatter is being
102     * constructed.
103     */

104     public Formatter(Class JavaDoc kitClass) {
105         this.kitClass = kitClass;
106         Settings.addSettingsChangeListener(this);
107     }
108
109     /** Get the kit-class for which this formatter is constructed. */
110     public Class JavaDoc getKitClass() {
111         return kitClass;
112     }
113
114     public void settingsChange(SettingsChangeEvent evt) {
115         String JavaDoc settingName = (evt != null) ? evt.getSettingName() : null;
116         if (!inited || settingName == null || SettingsNames.TAB_SIZE.equals(settingName)) {
117             if (!customTabSize) {
118                 tabSize = SettingsUtil.getInteger(kitClass, SettingsNames.TAB_SIZE,
119                                                   SettingsDefaults.defaultTabSize);
120             }
121         }
122
123         // Shift-width often depends on the rest of parameters
124
if (!customShiftWidth) {
125             Object JavaDoc shw = Settings.getValue(kitClass, SettingsNames.INDENT_SHIFT_WIDTH);
126             if (shw instanceof Integer JavaDoc) {
127                 shiftWidth = (Integer JavaDoc)shw;
128             }
129         }
130
131         if (!inited || settingName == null || SettingsNames.EXPAND_TABS.equals(settingName)) {
132             if (!customExpandTabs) {
133                 expandTabs = SettingsUtil.getBoolean(kitClass, SettingsNames.EXPAND_TABS,
134                                                      SettingsDefaults.defaultExpandTabs);
135             }
136         }
137         if (!inited || settingName == null || SettingsNames.SPACES_PER_TAB.equals(settingName)) {
138             if (!customSpacesPerTab) {
139                 spacesPerTab = SettingsUtil.getInteger(kitClass, SettingsNames.SPACES_PER_TAB,
140                                                        SettingsDefaults.defaultSpacesPerTab);
141             }
142         }
143
144         inited = true;
145     }
146
147     /** Get the number of spaces the TAB character ('\t') visually represents
148      * for non-BaseDocument documents. It shouldn't be used for BaseDocument
149      * based documents. The reason for that is that the returned value
150      * reflects the value of the setting for the kit class over which
151      * this formatter was constructed. However it's possible that the kit class of
152      * the document being formatted is different than the kit of the formatter.
153      * For example java document could be formatted by html formatter.
154      * Therefore <code>BaseDocument.getTabSize()</code> must be used
155      * for BaseDocuments to reflect the document's own tabsize.
156      * @see BaseDocument.getTabSize()
157      */

158     public int getTabSize() {
159         if (!customTabSize && !inited) {
160             settingsChange(null);
161         }
162
163         return tabSize;
164     }
165
166     /** Set the number of spaces the TAB character ('\t') visually represents
167      * for non-BaseDocument documents. It doesn't affect BaseDocument
168      * based documents.
169      *
170      * @see getTabSize()
171      * @see BaseDocument.setTabSize()
172      */

173     public void setTabSize(int tabSize) {
174         customTabSize = true;
175         this.tabSize = tabSize;
176     }
177
178
179     /** Get the width of one indentation level for non-BaseDocument documents.
180      * The algorithm first checks whether there's a value for the INDENT_SHIFT_WIDTH
181      * setting. If so it uses it, otherwise it uses <code>getSpacesPerTab()</code>
182      *
183      * @see setShiftWidth()
184      * @see getSpacesPerTab()
185      */

186     public int getShiftWidth() {
187         if (!customShiftWidth && !inited) {
188             settingsChange(null);
189         }
190
191         return (shiftWidth != null) ? shiftWidth.intValue() : getSpacesPerTab();
192     }
193
194     /** Set the width of one indentation level for non-BaseDocument documents.
195      * It doesn't affect BaseDocument based documents.
196      *
197      * @see getShiftWidth()
198      */

199     public void setShiftWidth(int shiftWidth) {
200         customShiftWidth = true;
201         if (this.shiftWidth == null || this.shiftWidth.intValue() != shiftWidth) {
202             this.shiftWidth = new Integer JavaDoc(shiftWidth);
203         }
204     }
205
206     /** Should the typed tabs be expanded to the spaces? */
207     public boolean expandTabs() {
208         if (!customExpandTabs && !inited) {
209             settingsChange(null);
210         }
211
212         return expandTabs;
213     }
214
215     public void setExpandTabs(boolean expandTabs) {
216         customExpandTabs = true;
217         this.expandTabs = expandTabs;
218     }
219
220     /** Get the number of spaces that should be inserted into the document
221     * instead of one typed tab.
222     */

223     public int getSpacesPerTab() {
224         if (!customSpacesPerTab && !inited) {
225             settingsChange(null);
226         }
227
228         return spacesPerTab;
229     }
230
231     public void setSpacesPerTab(int spacesPerTab) {
232         customSpacesPerTab = true;
233         this.spacesPerTab = spacesPerTab;
234     }
235
236     static String JavaDoc getIndentString(int indent, boolean expandTabs, int tabSize) {
237         if (indent <= 0) {
238             return "";
239         }
240
241         if (expandTabs) { // store in 0th slot
242
tabSize = 0;
243         }
244
245         synchronized (Settings.class) {
246             boolean large = (tabSize >= indentStringCache.length)
247                 || (indent > ISC_MAX_INDENT_SIZE); // indexed by (indent - 1)
248
String JavaDoc indentString = null;
249             String JavaDoc[] tabCache = null;
250             if (!large) {
251                 tabCache = indentStringCache[tabSize]; // cache for this tab
252
if (tabCache == null) {
253                     tabCache = new String JavaDoc[ISC_MAX_INDENT_SIZE];
254                     indentStringCache[tabSize] = tabCache;
255                 }
256                 indentString = tabCache[indent - 1];
257             }
258
259             if (indentString == null) {
260                 indentString = Analyzer.getIndentString(indent, expandTabs, tabSize);
261
262                 if (!large) {
263                     tabCache[indent - 1] = indentString;
264                 }
265             }
266
267             return indentString;
268         }
269     }
270
271     public String JavaDoc getIndentString(BaseDocument doc, int indent) {
272         return getIndentString(indent, expandTabs(), doc.getTabSize());
273     }
274         
275         
276     /** Get the string that is appropriate for the requested indentation.
277     * The returned string respects the <tt>expandTabs()</tt> and
278     * the <tt>getTabSize()</tt> and will contain either spaces only
279     * or fully or partially tabs as necessary.
280     */

281     public String JavaDoc getIndentString(int indent) {
282         return getIndentString(indent, expandTabs(), getTabSize());
283     }
284
285     /** Modify the line to move the text starting at dotPos one tab
286      * column to the right. Whitespace preceeding dotPos may be
287      * replaced by a TAB character if tabs expanding is on.
288      * @param doc document to operate on
289      * @param dotPos insertion point
290      */

291     public void insertTabString(BaseDocument doc, int dotPos)
292     throws BadLocationException JavaDoc {
293         doc.atomicLock();
294         try {
295             // Determine first white char before dotPos
296
int rsPos = Utilities.getRowStart(doc, dotPos);
297             int startPos = Utilities.getFirstNonWhiteBwd(doc, dotPos, rsPos);
298             startPos = (startPos >= 0) ? (startPos + 1) : rsPos;
299             
300             int startCol = Utilities.getVisualColumn(doc, startPos);
301             int endCol = Utilities.getNextTabColumn(doc, dotPos);
302             String JavaDoc tabStr = Analyzer.getWhitespaceString(startCol, endCol, expandTabs(), doc.getTabSize());
303             
304             // Search for the first non-common char
305
char[] removeChars = doc.getChars(startPos, dotPos - startPos);
306             int ind = 0;
307             while (ind < removeChars.length && removeChars[ind] == tabStr.charAt(ind)) {
308                 ind++;
309             }
310             
311             startPos += ind;
312             doc.remove(startPos, dotPos - startPos);
313             doc.insertString(startPos, tabStr.substring(ind), null);
314             
315         } finally {
316             doc.atomicUnlock();
317         }
318     }
319
320     /** Change the indent of the given row. Document is atomically locked
321     * during this operation.
322     */

323     public void changeRowIndent(BaseDocument doc, int pos, int newIndent)
324     throws BadLocationException JavaDoc {
325         doc.atomicLock();
326         try {
327             if (newIndent < 0) {
328                 newIndent = 0;
329             }
330             int firstNW = Utilities.getRowFirstNonWhite(doc, pos);
331             if (firstNW == -1) { // valid first non-blank
332
firstNW = Utilities.getRowEnd(doc, pos);
333             }
334             int replacePos = Utilities.getRowStart(doc, pos);
335             int removeLen = firstNW - replacePos;
336             CharSequence JavaDoc removeText = DocumentUtilities.getText(doc, replacePos, removeLen);
337             String JavaDoc newIndentText = getIndentString(doc, newIndent);
338             if (CharSequenceUtilities.startsWith(newIndentText, removeText)) {
339                 // Skip removeLen chars at start
340
newIndentText = newIndentText.substring(removeLen);
341                 replacePos += removeLen;
342                 removeLen = 0;
343             } else if (CharSequenceUtilities.endsWith(newIndentText, removeText)) {
344                 // Skip removeLen chars at the end
345
newIndentText = newIndentText.substring(0, newIndentText.length() - removeLen);
346                 removeLen = 0;
347             }
348             
349             if (removeLen != 0) {
350                 doc.remove(replacePos, removeLen);
351             }
352
353             doc.insertString(replacePos, newIndentText, null);
354         } finally {
355             doc.atomicUnlock();
356         }
357     }
358
359     /** Increase/decrease indentation of the block of the code. Document
360     * is atomically locked during the operation.
361     * @param doc document to operate on
362     * @param startPos starting line position
363     * @param endPos ending line position
364     * @param shiftCnt positive/negative count of shiftwidths by which indentation
365     * should be shifted right/left
366     */

367     public void changeBlockIndent(BaseDocument doc, int startPos, int endPos,
368                                   int shiftCnt) throws BadLocationException JavaDoc {
369
370        
371         GuardedDocument gdoc = (doc instanceof GuardedDocument)
372                                ? (GuardedDocument)doc : null;
373         if (gdoc != null){
374             for (int i = startPos; i<endPos; i++){
375                 if (gdoc.isPosGuarded(i)){
376                     java.awt.Toolkit.getDefaultToolkit().beep();
377                     return;
378                 }
379             }
380         }
381         
382         doc.atomicLock();
383         try {
384
385             int indentDelta = shiftCnt * doc.getShiftWidth();
386             if (endPos > 0 && Utilities.getRowStart(doc, endPos) == endPos) {
387                 endPos--;
388             }
389
390             int pos = Utilities.getRowStart(doc, startPos );
391             for (int lineCnt = Utilities.getRowCount(doc, startPos, endPos);
392                     lineCnt > 0; lineCnt--
393                 ) {
394                 int indent = Utilities.getRowIndent(doc, pos);
395                 if (Utilities.isRowWhite(doc, pos)) {
396                     indent = -indentDelta; // zero indentation for white line
397
}
398                 changeRowIndent(doc, pos, Math.max(indent + indentDelta, 0));
399                 pos = Utilities.getRowStart(doc, pos, +1);
400             }
401
402         } finally {
403             doc.atomicUnlock();
404         }
405     }
406
407     /** Shift line either left or right */
408     public void shiftLine(BaseDocument doc, int dotPos, boolean right)
409     throws BadLocationException JavaDoc {
410         int ind = doc.getShiftWidth();
411         if (!right) {
412             ind = -ind;
413         }
414
415         if (Utilities.isRowWhite(doc, dotPos)) {
416             ind += Utilities.getVisualColumn(doc, dotPos);
417         } else {
418             ind += Utilities.getRowIndent(doc, dotPos);
419         }
420         ind = Math.max(ind, 0);
421         changeRowIndent(doc, dotPos, ind);
422     }
423
424     /** Reformat a block of code.
425     * @param doc document to work with
426     * @param startOffset offset at which the formatting starts
427     * @param endOffset offset at which the formatting ends
428     * @return length of the reformatted code
429     */

430     public int reformat(BaseDocument doc, int startOffset, int endOffset)
431     throws BadLocationException JavaDoc {
432         try {
433             CharArrayWriter JavaDoc cw = new CharArrayWriter JavaDoc();
434             Writer JavaDoc w = createWriter(doc, startOffset, cw);
435             w.write(doc.getChars(startOffset, endOffset - startOffset));
436             w.close();
437             String JavaDoc out = new String JavaDoc(cw.toCharArray());
438             doc.remove(startOffset, endOffset - startOffset);
439             doc.insertString(startOffset, out, null);
440             return out.length();
441         } catch (IOException JavaDoc e) {
442             Utilities.annotateLoggable(e);
443             return 0;
444         }
445     }
446
447     /** Indents the current line. Should not affect any other
448     * lines.
449     * @param doc the document to work on
450     * @param offset the offset of a character on the line
451     * @return new offset of the original character
452     */

453     public int indentLine(Document JavaDoc doc, int offset) {
454         return offset;
455     }
456
457     /** Inserts new line at given position and indents the new line with
458     * spaces.
459     *
460     * @param doc the document to work on
461     * @param offset the offset of a character on the line
462     * @return new offset to place cursor to
463     */

464     public int indentNewLine(Document JavaDoc doc, int offset) {
465         try {
466             doc.insertString(offset, "\n", null); // NOI18N
467
offset++;
468
469         } catch (GuardedException e) {
470             java.awt.Toolkit.getDefaultToolkit().beep();
471
472         } catch (BadLocationException JavaDoc e) {
473             Utilities.annotateLoggable(e);
474         }
475
476         return offset;
477     }
478
479     /** Creates a writer that formats text that is inserted into it.
480     * The writer should not modify the document but use the
481     * provided writer to write to. Usually the underlaying writer
482     * will modify the document itself and optionally it can remember
483     * the current position in document. That is why the newly created
484     * writer should do no buffering.
485     * <P>
486     * The provided document and pos are only informational,
487     * should not be modified but only used to find correct indentation
488     * strategy.
489     *
490     * @param doc document
491     * @param offset position to begin inserts at
492     * @param writer writer to write to
493     * @return new writer that will format written text and pass it
494     * into the writer
495     */

496     public Writer JavaDoc createWriter(Document JavaDoc doc, int offset, Writer JavaDoc writer) {
497         return writer;
498     }
499
500 }
501
Popular Tags