KickJava   Java API By Example, From Geeks To Geeks.

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


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

19
20 package org.netbeans.editor.ext;
21
22 import java.io.CharArrayWriter JavaDoc;
23 import java.io.Writer JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import javax.swing.text.Document JavaDoc;
31 import javax.swing.text.BadLocationException JavaDoc;
32 import javax.swing.text.JTextComponent JavaDoc;
33 import org.netbeans.editor.BaseKit;
34 import org.netbeans.editor.BaseDocument;
35 import org.netbeans.editor.Formatter;
36 import org.netbeans.editor.Settings;
37 import org.netbeans.editor.SettingsUtil;
38 import org.netbeans.editor.SettingsChangeEvent;
39 import org.netbeans.editor.Utilities;
40 import org.netbeans.editor.GuardedException;
41 import org.netbeans.editor.Acceptor;
42 import org.netbeans.editor.AcceptorFactory;
43 import org.netbeans.editor.Syntax;
44
45 /**
46 * Unlike the formatter class, the ExtFormatter concentrates
47 * on providing a support for the real formatting process.
48 * Each formatter (there's only one per each kit) can contain
49 * one or more formatting layers. The <tt>FormatLayer</tt>
50 * operates over the chain of the tokens provided
51 * by the <tt>FormatWriter</tt>. The formatting consist
52 * of changing the chain of the tokens until it gets
53 * the desired look.
54 * Each formatting requires a separate instance
55 * of <tt>FormatWriter</tt> but the same set of format-layers
56 * is used for all the format-writers. Although the base
57 * implementation is synchronized so that only one
58 * format-writer at time is processed by each format-writer,
59 * in general it's not necessary.
60 * The basic implementation processes all the format-layers
61 * sequentialy in the order they were added to the formatter
62 * but this can be redefined.
63 * The <tt>getSettingValue</tt> enables to get the up-to-date
64 * value for the particular setting.
65 *
66 * @author Miloslav Metelka
67 * @version 1.00
68 */

69
70 public class ExtFormatter extends Formatter implements FormatLayer {
71
72     /** List holding the format layers */
73     private List JavaDoc formatLayerList = new ArrayList JavaDoc();
74
75     /** Use this instead of testing by containsKey() */
76     private static final Object JavaDoc NULL_VALUE = new Object JavaDoc();
77
78     /** Map that contains the requested [setting-name, setting-value] pairs */
79     private HashMap JavaDoc settingsMap = new HashMap JavaDoc();
80
81     /** Contains the names of the keys that were turned
82      * into custom settings and are no longer read from
83      * the Settings.
84      */

85     private HashMap JavaDoc customSettingsNamesMap = new HashMap JavaDoc();
86
87     private Acceptor indentHotCharsAcceptor;
88     private boolean reindentWithTextBefore;
89
90     public ExtFormatter(Class JavaDoc kitClass) {
91         super(kitClass);
92
93         initFormatLayers();
94     }
95
96     /** Add the desired format-layers to the formatter */
97     protected void initFormatLayers() {
98     }
99
100     /** Return the name of this formatter. By default
101      * it's the name of the kit-class for which it's created
102      * without the package name.
103      */

104     public String JavaDoc getName() {
105         return getKitClass().getName().substring(
106                 getKitClass().getName().lastIndexOf('.') + 1);
107     }
108
109     public void settingsChange(SettingsChangeEvent evt) {
110         super.settingsChange(evt);
111         String JavaDoc settingName = (evt != null) ? evt.getSettingName() : null;
112
113         Class JavaDoc kitClass = getKitClass();
114         Iterator JavaDoc eit = settingsMap.entrySet().iterator();
115         while (eit.hasNext()) {
116             Map.Entry JavaDoc e = (Map.Entry JavaDoc)eit.next();
117             if (settingName == null || e.getKey().equals(e.getKey())) {
118                 if (!customSettingsNamesMap.containsKey(e.getKey())) { // not custom
119
e.setValue(Settings.getValue(kitClass, (String JavaDoc)e.getKey()));
120                 }
121             }
122         }
123         
124         indentHotCharsAcceptor = SettingsUtil.getAcceptor(kitClass,
125             ExtSettingsNames.INDENT_HOT_CHARS_ACCEPTOR,
126             AcceptorFactory.FALSE);
127
128         reindentWithTextBefore = SettingsUtil.getBoolean(kitClass,
129             ExtSettingsNames.REINDENT_WITH_TEXT_BEFORE,
130             false);
131     }
132
133     /** Get the value of the given setting.
134     * @param settingName name of the setting to get.
135     */

136     public Object JavaDoc getSettingValue(String JavaDoc settingName) {
137         synchronized (Settings.class) {
138             Object JavaDoc value = settingsMap.get(settingName);
139             if (value == null && !customSettingsNamesMap.containsKey(settingName)) {
140                 value = Settings.getValue(getKitClass(), settingName);
141                 if (value == null) {
142                     value = NULL_VALUE;
143                 }
144                 settingsMap.put(settingName, value);
145             }
146             return (value != NULL_VALUE) ? value : null;
147         }
148     }
149
150     /** This method allows to set a custom value to a setting thus
151      * overriding the value retrieved from the <tt>Settings</tt>.
152      * Once done the value is no longer synchronized with the changes
153      * in <tt>Settings</tt> for the particular setting.
154      * There's a map holding the names of all the custom
155      * settings.
156      */

157     public void setSettingValue(String JavaDoc settingName, Object JavaDoc settingValue) {
158         synchronized (Settings.class) {
159             customSettingsNamesMap.put(settingName, settingName);
160             settingsMap.put(settingName, settingValue);
161         }
162     }
163
164     /** Add the new format layer to the layer hierarchy.
165     */

166     public synchronized void addFormatLayer(FormatLayer layer) {
167         formatLayerList.add(layer);
168     }
169
170     /** Replace the format-layer with the layerName
171     * with the the given layer. If there's no such layer with the same
172     * name, the layer is not replaced and false is returned.
173     */

174     public synchronized boolean replaceFormatLayer(String JavaDoc layerName, FormatLayer layer) {
175         int cnt = formatLayerList.size();
176         for (int i = 0; i < cnt; i++) {
177             if (layerName.equals(((FormatLayer)formatLayerList.get(i)).getName())) {
178                 formatLayerList.set(i, layer);
179                 return true;
180             }
181         }
182         return false;
183     }
184
185     /** Remove the first layer which has the same name as the given one.
186     */

187     public synchronized void removeFormatLayer(String JavaDoc layerName) {
188         Iterator JavaDoc it = formatLayerIterator();
189         while (it.hasNext()) {
190             if (layerName.equals(((FormatLayer)it.next()).getName())) {
191                 it.remove();
192                 return;
193             }
194         }
195     }
196
197     /** Get the iterator over the format layers.
198     */

199     public Iterator JavaDoc formatLayerIterator() {
200         return formatLayerList.iterator();
201     }
202
203     /** Whether do no formatting at all. If this method returns true,
204      * the FormatWriter will simply write its input into the underlying
205      * writer.
206      */

207     public boolean isSimple() {
208         return false;
209     }
210
211     /** Called by format-writer to do the format */
212     public synchronized void format(FormatWriter fw) {
213         boolean done = false;
214         int safetyCounter = 0;
215         do {
216             // Mark the chain as unmodified at the begining
217
fw.setChainModified(false);
218             fw.setRestartFormat(false);
219
220             Iterator JavaDoc it = formatLayerIterator();
221             while (it.hasNext()) {
222                 ((FormatLayer)it.next()).format(fw);
223                 if (fw.isRestartFormat()) {
224                     break;
225                 }
226             }
227
228             if (!it.hasNext() && !fw.isRestartFormat()) {
229                 done = true;
230             }
231
232             if (safetyCounter > 1000) { // prevent infinite loop
233
new Exception JavaDoc("Indentation infinite loop detected").printStackTrace(); // NOI18N
234
break;
235             }
236         } while (!done);
237     }
238
239     /** Reformat a block of code.
240     * @param doc document to work with
241     * @param startOffset position at which the formatting starts
242     * @param endOffset position at which the formatting ends
243     * @param indentOnly whether just the indentation should be changed
244     * or regular formatting should be performed.
245     * @return formatting writer. The text was already reformatted
246     * but the writer can contain useful information.
247     */

248     public Writer JavaDoc reformat(BaseDocument doc, int startOffset, int endOffset,
249     boolean indentOnly) throws BadLocationException JavaDoc, IOException JavaDoc {
250         CharArrayWriter JavaDoc cw = new CharArrayWriter JavaDoc();
251         Writer JavaDoc w = createWriter(doc, startOffset, cw);
252         FormatWriter fw = (w instanceof FormatWriter) ? (FormatWriter)w : null;
253         
254         boolean fix5620 = true; // whether apply fix for #5620 or not
255

256         if (fw != null) {
257             fw.setIndentOnly(indentOnly);
258             if (fix5620) {
259                 fw.setReformatting(true); // #5620
260
}
261         }
262
263         w.write(doc.getChars(startOffset, endOffset - startOffset));
264         w.close();
265
266         if (!fix5620 || fw == null) { // #5620 - for (fw != null) the doc was already modified
267
String JavaDoc out = new String JavaDoc(cw.toCharArray());
268             doc.remove(startOffset, endOffset - startOffset);
269             doc.insertString(startOffset, out, null);
270         }
271         
272         return w;
273     }
274
275     /** Fix of #5620 - same method exists in Formatter (predecessor */
276     public int reformat(BaseDocument doc, int startOffset, int endOffset)
277     throws BadLocationException JavaDoc {
278         try {
279             javax.swing.text.Position JavaDoc pos = doc.createPosition(endOffset);
280             reformat(doc, startOffset, endOffset, false);
281             return pos.getOffset() - startOffset;
282         } catch (IOException JavaDoc e) {
283             e.printStackTrace();
284             return 0;
285         }
286     }
287
288     /** Get the block to be reformatted after keystroke was pressed.
289      * @param target component to which the text was typed. Caaret position
290      * can be checked etc.
291      * @param typedText text (usually just one character) that the user has typed.
292      * @return block of the code to be reformatted or null if nothing should
293      * reformatted. It can return block containing just one character. The caller
294      * usually expands even one character to the whole line because less than
295      * the whole line usually doesn't provide enough possibilities for formatting.
296      * @see ExtKit.ExtDefaultKeyTypedAction.checkIndentHotChars()
297      */

298     public int[] getReformatBlock(JTextComponent JavaDoc target, String JavaDoc typedText) {
299         if (indentHotCharsAcceptor == null) { // init if necessary
300
settingsChange(null);
301         }
302
303         if (indentHotCharsAcceptor.accept(typedText.charAt(0))) {
304             /* This is bugfix 10771. See the issue for problem description.
305              * The behaviour before fix was that whenever the lbrace is
306              * entered, the line is indented. This make no sense if a text
307              * exist on the line before the lbrace. In this case we
308              * simply will not indent the line. This is handled by the hasTextBefore
309              * check
310              */

311             if(!reindentWithTextBefore) {
312                 if(hasTextBefore(target, typedText)) {
313                     return null;
314                 }
315             }
316             int dotPos = target.getCaret().getDot();
317             return new int[] { Math.max(dotPos - 1, 0), dotPos };
318             
319         } else {
320             return null;
321         }
322     }
323
324     protected boolean hasTextBefore(JTextComponent JavaDoc target, String JavaDoc typedText) {
325         BaseDocument doc = Utilities.getDocument(target);
326         int dotPos = target.getCaret().getDot();
327         try {
328             int fnw = Utilities.getRowFirstNonWhite(doc, dotPos);
329             return dotPos != fnw+typedText.length();
330         } catch (BadLocationException JavaDoc e) {
331             return false;
332         }
333     }
334
335     /** Create the indentation writer.
336     */

337     public Writer JavaDoc createWriter(Document JavaDoc doc, int offset, Writer JavaDoc writer) {
338         return new FormatWriter(this, doc, offset, writer, false);
339     }
340
341     /** Indents the current line. Should not affect any other
342     * lines.
343     * @param doc the document to work on
344     * @param offset the offset of a character on the line
345     * @return new offset of the original character
346     */

347     public int indentLine(Document JavaDoc doc, int offset) {
348         if (doc instanceof BaseDocument) {
349             try {
350                 BaseDocument bdoc = (BaseDocument)doc;
351                 int lineStart = Utilities.getRowStart(bdoc, offset);
352                 int nextLineStart = Utilities.getRowStart(bdoc, offset, 1);
353                 if (nextLineStart < 0) { // end of doc
354
nextLineStart = bdoc.getLength();
355                 }
356                 reformat(bdoc, lineStart, nextLineStart, false);
357                 return Utilities.getRowEnd(bdoc, lineStart);
358             } catch (GuardedException e) {
359                 java.awt.Toolkit.getDefaultToolkit().beep();
360
361             } catch (BadLocationException JavaDoc e) {
362                 Utilities.annotateLoggable(e);
363             } catch (IOException JavaDoc e) {
364                 Utilities.annotateLoggable(e);
365             }
366
367             return offset;
368
369         }
370
371         return super.indentLine(doc, offset);
372     }
373     
374     /** Returns offset of EOL for the white line */
375     protected int getEOLOffset(BaseDocument bdoc, int offset) throws BadLocationException JavaDoc{
376         return Utilities.getRowEnd(bdoc, offset);
377     }
378
379     /** Inserts new line at given position and indents the new line with
380     * spaces.
381     *
382     * @param doc the document to work on
383     * @param offset the offset of a character on the line
384     * @return new offset to place cursor to
385     */

386     public int indentNewLine(Document JavaDoc doc, int offset) {
387         if (doc instanceof BaseDocument) {
388             BaseDocument bdoc = (BaseDocument)doc;
389             boolean newLineInserted = false;
390
391             bdoc.atomicLock();
392             try {
393                 bdoc.insertString(offset, "\n", null); // NOI18N
394
offset++;
395                 newLineInserted = true;
396
397                 int eolOffset = Utilities.getRowEnd(bdoc, offset);
398
399                 // Try to change the indent of the new line
400
// It may fail when inserting '\n' before the guarded block
401
Writer JavaDoc w = reformat(bdoc, offset, eolOffset, true);
402
403                 // Find the caret position
404
eolOffset = Utilities.getRowFirstNonWhite(bdoc, offset);
405                 if (eolOffset < 0) { // white line
406
eolOffset = getEOLOffset(bdoc, offset);
407                 }
408
409                 offset = eolOffset;
410                 
411                 // Resulting offset (caret position) can be shifted
412
if (w instanceof FormatWriter) {
413                     offset += ((FormatWriter)w).getIndentShift();
414                 }
415
416             } catch (GuardedException e) {
417                 // Possibly couldn't insert additional indentation
418
// at the begining of the guarded block
419
// but the initial '\n' could be fine
420
if (!newLineInserted) {
421                     java.awt.Toolkit.getDefaultToolkit().beep();
422                 }
423
424             } catch (BadLocationException JavaDoc e) {
425                 Utilities.annotateLoggable(e);
426             } catch (IOException JavaDoc e) {
427                 Utilities.annotateLoggable(e);
428             } finally {
429                 bdoc.atomicUnlock();
430             }
431
432         } else { // not BaseDocument
433
try {
434                 doc.insertString (offset, "\n", null); // NOI18N
435
offset++;
436             } catch (BadLocationException JavaDoc ex) {
437             }
438         }
439
440         return offset;
441     }
442
443     /** Whether the formatter accepts the given syntax
444      * that will be used for parsing the text passed to
445      * the FormatWriter.
446      * @param syntax syntax to be tested.
447      * @return true whether this formatter is able to process
448      * the tokens created by the syntax or false otherwise.
449      */

450     protected boolean acceptSyntax(Syntax syntax) {
451         return true;
452     }
453
454     /** Simple formatter */
455     public static class Simple extends ExtFormatter {
456
457         public Simple(Class JavaDoc kitClass) {
458             super(kitClass);
459         }
460
461         public boolean isSimple() {
462             return true;
463         }
464         
465         /** Returns offset of EOL for the white line */
466         protected int getEOLOffset(BaseDocument bdoc, int offset) throws BadLocationException JavaDoc{
467             return offset;
468         }
469         
470     }
471
472 }
473
Popular Tags