KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > lib > editor > util > swing > DocumentUtilities


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.lib.editor.util.swing;
21
22 import java.util.Map JavaDoc;
23 import javax.swing.event.DocumentEvent JavaDoc;
24 import javax.swing.event.DocumentListener JavaDoc;
25 import javax.swing.text.BadLocationException JavaDoc;
26 import javax.swing.text.Document JavaDoc;
27 import javax.swing.text.Element JavaDoc;
28 import javax.swing.text.Segment JavaDoc;
29 import javax.swing.text.StyledDocument JavaDoc;
30 import javax.swing.undo.CannotRedoException JavaDoc;
31 import javax.swing.undo.CannotUndoException JavaDoc;
32 import javax.swing.undo.UndoableEdit JavaDoc;
33 import org.netbeans.lib.editor.util.AbstractCharSequence;
34 import org.netbeans.lib.editor.util.CompactMap;
35
36 /**
37  * Various utility methods related to swing text documents.
38  *
39  * @author Miloslav Metelka
40  * @since 1.4
41  */

42
43 public final class DocumentUtilities {
44     
45     private static final Object JavaDoc TYPING_MODIFICATION_DOCUMENT_PROPERTY = new Object JavaDoc();
46     
47     private static final Object JavaDoc TYPING_MODIFICATION_KEY = new Object JavaDoc();
48     
49     
50     private DocumentUtilities() {
51         // No instances
52
}
53
54     /**
55      * Add document listener to document with given priority
56      * or default to using regular {@link Document#addDocumentListener(DocumentListener)}
57      * if the given document is not listener priority aware.
58      *
59      * @param doc document to which the listener should be added.
60      * @param listener document listener to add.
61      * @param priority priority with which the listener should be added.
62      * If the document does not support document listeners ordering
63      * then the listener is added in a regular way by using
64      * {@link javax.swing.text.Document#addDocumentListener(
65      * javax.swing.event.DocumentListener)} method.
66      */

67     public static void addDocumentListener(Document JavaDoc doc, DocumentListener JavaDoc listener,
68     DocumentListenerPriority priority) {
69         if (!addPriorityDocumentListener(doc, listener, priority))
70             doc.addDocumentListener(listener);
71     }
72     
73     /**
74      * Suitable for document implementations - adds document listener
75      * to document with given priority and does not do anything
76      * if the given document is not listener priority aware.
77      * <br/>
78      * Using this method in the document impls and defaulting
79      * to super.addDocumentListener() in case it returns false
80      * will ensure that there won't be an infinite loop in case the super constructors
81      * would add some listeners prior initing of the priority listening.
82      *
83      * @param doc document to which the listener should be added.
84      * @param listener document listener to add.
85      * @param priority priority with which the listener should be added.
86      * @return true if the priority listener was added or false if the document
87      * does not support priority listening.
88      */

89     public static boolean addPriorityDocumentListener(Document JavaDoc doc, DocumentListener JavaDoc listener,
90     DocumentListenerPriority priority) {
91         PriorityDocumentListenerList priorityDocumentListenerList
92                 = (PriorityDocumentListenerList)doc.getProperty(PriorityDocumentListenerList.class);
93         if (priorityDocumentListenerList != null) {
94             priorityDocumentListenerList.add(listener, priority.getPriority());
95             return true;
96         } else
97             return false;
98     }
99
100     /**
101      * Remove document listener that was previously added to the document
102      * with given priority or use default {@link Document#removeDocumentListener(DocumentListener)}
103      * if the given document is not listener priority aware.
104      *
105      * @param doc document from which the listener should be removed.
106      * @param listener document listener to remove.
107      * @param priority priority with which the listener should be removed.
108      * It should correspond to the priority with which the listener
109      * was added originally.
110      */

111     public static void removeDocumentListener(Document JavaDoc doc, DocumentListener JavaDoc listener,
112     DocumentListenerPriority priority) {
113         if (!removePriorityDocumentListener(doc, listener, priority))
114             doc.removeDocumentListener(listener);
115     }
116
117     /**
118      * Suitable for document implementations - removes document listener
119      * from document with given priority and does not do anything
120      * if the given document is not listener priority aware.
121      * <br/>
122      * Using this method in the document impls and defaulting
123      * to super.removeDocumentListener() in case it returns false
124      * will ensure that there won't be an infinite loop in case the super constructors
125      * would remove some listeners prior initing of the priority listening.
126      *
127      * @param doc document from which the listener should be removed.
128      * @param listener document listener to remove.
129      * @param priority priority with which the listener should be removed.
130      * @return true if the priority listener was removed or false if the document
131      * does not support priority listening.
132      */

133     public static boolean removePriorityDocumentListener(Document JavaDoc doc, DocumentListener JavaDoc listener,
134     DocumentListenerPriority priority) {
135         PriorityDocumentListenerList priorityDocumentListenerList
136                 = (PriorityDocumentListenerList)doc.getProperty(PriorityDocumentListenerList.class);
137         if (priorityDocumentListenerList != null) {
138             priorityDocumentListenerList.remove(listener, priority.getPriority());
139             return true;
140         } else
141             return false;
142     }
143
144     /**
145      * This method should be used by swing document implementations that
146      * want to support document listeners prioritization.
147      * <br>
148      * It should be called from document's constructor in the following way:<pre>
149      *
150      * class MyDocument extends AbstractDocument {
151      *
152      * MyDocument() {
153      * super.addDocumentListener(DocumentUtilities.initPriorityListening(this));
154      * }
155      *
156      * public void addDocumentListener(DocumentListener listener) {
157      * if (!DocumentUtilities.addDocumentListener(this, listener, DocumentListenerPriority.DEFAULT))
158      * super.addDocumentListener(listener);
159      * }
160      *
161      * public void removeDocumentListener(DocumentListener listener) {
162      * if (!DocumentUtilities.removeDocumentListener(this, listener, DocumentListenerPriority.DEFAULT))
163      * super.removeDocumentListener(listener);
164      * }
165      *
166      * }</pre>
167      *
168      *
169      * @param doc document to be initialized.
170      * @return the document listener instance that should be added as a document
171      * listener typically by using <code>super.addDocumentListener()</code>
172      * in document's constructor.
173      * @throws IllegalStateException when the document already has
174      * the property initialized.
175      */

176     public static DocumentListener JavaDoc initPriorityListening(Document JavaDoc doc) {
177         if (doc.getProperty(PriorityDocumentListenerList.class) != null) {
178             throw new IllegalStateException JavaDoc(
179                     "PriorityDocumentListenerList already initialized for doc=" + doc); // NOI18N
180
}
181         PriorityDocumentListenerList listener = new PriorityDocumentListenerList();
182         doc.putProperty(PriorityDocumentListenerList.class, listener);
183         return listener;
184     }
185
186     /**
187      * Mark that the ongoing document modification(s) will be caused
188      * by user's typing.
189      * It should be used by default-key-typed-action and the actions
190      * for backspace and delete keys.
191      * <br/>
192      * The document listeners being fired may
193      * query it by using {@link #isTypingModification(Document)}.
194      * This method should always be used in the following pattern:
195      * <pre>
196      * DocumentUtilities.setTypingModification(doc, true);
197      * try {
198      * doc.insertString(offset, typedText, null);
199      * } finally {
200      * DocumentUtilities.setTypingModification(doc, false);
201      * }
202      * </pre>
203      *
204      * @see #isTypingModification(Document)
205      */

206     public static void setTypingModification(Document JavaDoc doc, boolean typingModification) {
207         doc.putProperty(TYPING_MODIFICATION_DOCUMENT_PROPERTY, Boolean.valueOf(typingModification));
208     }
209     
210     /**
211      * This method should be used by document listeners to check whether
212      * the just performed document modification was caused by user's typing.
213      * <br/>
214      * Certain functionality such as code completion or code templates
215      * may benefit from that information. For example the java code completion
216      * should only react to the typed "." but not if the same string was e.g.
217      * pasted from the clipboard.
218      *
219      * @see #setTypingModification(Document, boolean)
220      */

221     public static boolean isTypingModification(Document JavaDoc doc) {
222         Boolean JavaDoc b = (Boolean JavaDoc)doc.getProperty(TYPING_MODIFICATION_DOCUMENT_PROPERTY);
223         return (b != null) ? b.booleanValue() : false;
224     }
225
226     /**
227      * @deprecated
228      * @see #isTypingModification(Document)
229      */

230     public static boolean isTypingModification(DocumentEvent JavaDoc evt) {
231         return isTypingModification(evt.getDocument());
232     }
233
234     /**
235      * Get text of the given document as char sequence.
236      * <br>
237      *
238      * @param doc document for which the charsequence is being obtained.
239      * @return non-null character sequence.
240      * <br>
241      * The returned character sequence should only be accessed under
242      * document's readlock (or writelock).
243      */

244     public static CharSequence JavaDoc getText(Document JavaDoc doc) {
245         CharSequence JavaDoc text = (CharSequence JavaDoc)doc.getProperty(CharSequence JavaDoc.class);
246         if (text == null) {
247             text = new DocumentCharSequence(doc);
248             doc.putProperty(CharSequence JavaDoc.class, text);
249         }
250         return text;
251     }
252     
253     /**
254      * Get a portion of text of the given document as char sequence.
255      * <br>
256      *
257      * @param doc document for which the charsequence is being obtained.
258      * @param offset starting offset of the charsequence to obtain.
259      * @param length length of the charsequence to obtain
260      * @return non-null character sequence.
261      * @exception BadLocationException some portion of the given range
262      * was not a valid part of the document. The location in the exception
263      * is the first bad position encountered.
264      * <br>
265      * The returned character sequence should only be accessed under
266      * document's readlock (or writelock).
267      */

268     public static CharSequence JavaDoc getText(Document JavaDoc doc, int offset, int length) throws BadLocationException JavaDoc {
269         CharSequence JavaDoc text = (CharSequence JavaDoc)doc.getProperty(CharSequence JavaDoc.class);
270         if (text == null) {
271             text = new DocumentCharSequence(doc);
272             doc.putProperty(CharSequence JavaDoc.class, text);
273         }
274         try {
275             return text.subSequence(offset, offset + length);
276         } catch (IndexOutOfBoundsException JavaDoc e) {
277             int badOffset = offset;
278             if (offset >= 0 && offset + length > text.length()) {
279                 badOffset = length;
280             }
281             throw new BadLocationException JavaDoc(e.getMessage(), badOffset);
282         }
283     }
284     
285     /**
286      * Document provider should call this method to allow for document event
287      * properties being stored in document events.
288      *
289      * @param evt document event to which the storage should be added.
290      * It must be an undoable edit allowing to add an edit.
291      */

292     public static void addEventPropertyStorage(DocumentEvent JavaDoc evt) {
293         // Parameter is DocumentEvent because it's more logical
294
if (!(evt instanceof UndoableEdit JavaDoc)) {
295             throw new IllegalStateException JavaDoc("evt not instanceof UndoableEdit: " + evt); // NOI18N
296
}
297         ((UndoableEdit JavaDoc)evt).addEdit(new EventPropertiesElementChange());
298     }
299     
300     /**
301      * Get a property of a given document event.
302      *
303      * @param evt non-null document event from which the property should be retrieved.
304      * @param key non-null key of the property.
305      * @return value for the given property.
306      */

307     public static Object JavaDoc getEventProperty(DocumentEvent JavaDoc evt, Object JavaDoc key) {
308         EventPropertiesElementChange change = (EventPropertiesElementChange)
309                 evt.getChange(EventPropertiesElement.INSTANCE);
310         return (change != null) ? change.getProperty(key) : null;
311     }
312     
313     /**
314      * Set a property of a given document event.
315      *
316      * @param evt non-null document event to which the property should be stored.
317      * @param key non-null key of the property.
318      * @param value for the given property.
319      */

320     public static void putEventProperty(DocumentEvent JavaDoc evt, Object JavaDoc key, Object JavaDoc value) {
321         EventPropertiesElementChange change = (EventPropertiesElementChange)
322                 evt.getChange(EventPropertiesElement.INSTANCE);
323         if (change == null) {
324             throw new IllegalStateException JavaDoc("addEventPropertyStorage() not called for evt=" + evt); // NOI18N
325
}
326         change.putProperty(key, value);
327     }
328     
329     /**
330      * Set a property of a given document event by using the given map entry.
331      * <br/>
332      * The present implementation is able to directly store instances
333      * of <code>CompactMap.MapEntry</code>. Other map entry implementations
334      * will be delegated to {@link #putEventProperty(DocumentEvent, Object, Object)}.
335      *
336      * @param evt non-null document event to which the property should be stored.
337      * @param mapEntry non-null map entry which should be stored.
338      * Generally after this method finishes the {@link #getEventProperty(DocumentEvent, Object)}
339      * will return <code>mapEntry.getValue()</code> for <code>mapEntry.getKey()</code> key.
340      */

341     public static void putEventProperty(DocumentEvent JavaDoc evt, Map.Entry JavaDoc mapEntry) {
342         if (mapEntry instanceof CompactMap.MapEntry) {
343             EventPropertiesElementChange change = (EventPropertiesElementChange)
344                     evt.getChange(EventPropertiesElement.INSTANCE);
345             if (change == null) {
346                 throw new IllegalStateException JavaDoc("addEventPropertyStorage() not called for evt=" + evt); // NOI18N
347
}
348             change.putEntry((CompactMap.MapEntry)mapEntry);
349
350         } else {
351             putEventProperty(evt, mapEntry.getKey(), mapEntry.getValue());
352         }
353     }
354     
355     /**
356      * Fix the given offset according to the performed modification.
357      *
358      * @param offset >=0 offset in a document.
359      * @param evt document event describing change in the document.
360      * @return offset updated by applying the document change to the offset.
361      */

362     public static int fixOffset(int offset, DocumentEvent JavaDoc evt) {
363         int modOffset = evt.getOffset();
364         if (evt.getType() == DocumentEvent.EventType.INSERT) {
365             if (offset >= modOffset) {
366                 offset += evt.getLength();
367             }
368         } else if (evt.getType() == DocumentEvent.EventType.REMOVE) {
369             if (offset > modOffset) {
370                 offset = Math.min(offset - evt.getLength(), modOffset);
371             }
372         }
373         return offset;
374     }
375     
376     /**
377      * Get text of the given document modification.
378      * <br/>
379      * It's implemented as retrieving of a <code>String.class</code>.
380      *
381      * @param evt document event describing either document insertion or removal
382      * (change event type events will produce null result).
383      * @return text that was inserted/removed from the document by the given
384      * document modification or null if that information is not provided
385      * by that document event.
386      */

387     public static String JavaDoc getModificationText(DocumentEvent JavaDoc evt) {
388         return (String JavaDoc)getEventProperty(evt, String JavaDoc.class);
389     }
390     
391     /**
392      * Get the paragraph element for the given document.
393      *
394      * @param doc non-null document instance.
395      * @param offset offset in the document >=0
396      * @return paragraph element containing the given offset.
397      */

398     public static Element JavaDoc getParagraphElement(Document JavaDoc doc, int offset) {
399         Element JavaDoc paragraph;
400         if (doc instanceof StyledDocument JavaDoc) {
401             paragraph = ((StyledDocument JavaDoc)doc).getParagraphElement(offset);
402         } else {
403             Element JavaDoc rootElem = doc.getDefaultRootElement();
404             int index = rootElem.getElementIndex(offset);
405             paragraph = rootElem.getElement(index);
406             if ((offset < paragraph.getStartOffset()) || (offset >= paragraph.getEndOffset())) {
407                 paragraph = null;
408             }
409         }
410         return paragraph;
411     }
412     
413     /**
414      * Get the root of the paragraph elements for the given document.
415      *
416      * @param doc non-null document instance.
417      * @return root element of the paragraph elements.
418      */

419     public static Element JavaDoc getParagraphRootElement(Document JavaDoc doc) {
420         if (doc instanceof StyledDocument JavaDoc) {
421             return ((StyledDocument JavaDoc)doc).getParagraphElement(0).getParentElement();
422         } else {
423             return doc.getDefaultRootElement().getElement(0).getParentElement();
424         }
425     }
426
427     /**
428      * Implementation of the character sequence for a generic document
429      * that does not provide its own implementation of character sequence.
430      */

431     private static final class DocumentCharSequence extends AbstractCharSequence.StringLike {
432         
433         private final Segment JavaDoc segment = new Segment JavaDoc();
434         
435         private final Document JavaDoc doc;
436         
437         DocumentCharSequence(Document JavaDoc doc) {
438             this.doc = doc;
439         }
440
441         public int length() {
442             return doc.getLength();
443         }
444
445         public synchronized char charAt(int index) {
446             try {
447                 doc.getText(index, 1, segment);
448             } catch (BadLocationException JavaDoc e) {
449                 throw new IndexOutOfBoundsException JavaDoc(e.getMessage()
450                     + " at offset=" + e.offsetRequested()); // NOI18N
451
}
452             return segment.array[segment.offset];
453         }
454
455     }
456     
457     /**
458      * Helper element used as a key in searching for an element change
459      * being a storage of the additional properties in a document event.
460      */

461     private static final class EventPropertiesElement implements Element JavaDoc {
462         
463         static final EventPropertiesElement INSTANCE = new EventPropertiesElement();
464         
465         public int getStartOffset() {
466             return 0;
467         }
468
469         public int getEndOffset() {
470             return 0;
471         }
472
473         public int getElementCount() {
474             return 0;
475         }
476
477         public int getElementIndex(int offset) {
478             return -1;
479         }
480
481         public Element JavaDoc getElement(int index) {
482             return null;
483         }
484
485         public boolean isLeaf() {
486             return true;
487         }
488
489         public Element JavaDoc getParentElement() {
490             return null;
491         }
492
493         public String JavaDoc getName() {
494             return "Helper element for modification text providing"; // NOI18N
495
}
496
497         public Document JavaDoc getDocument() {
498             return null;
499         }
500
501         public javax.swing.text.AttributeSet JavaDoc getAttributes() {
502             return null;
503         }
504         
505         public String JavaDoc toString() {
506             return getName();
507         }
508
509     }
510     
511     private static final class EventPropertiesElementChange
512     implements DocumentEvent.ElementChange JavaDoc, UndoableEdit JavaDoc {
513         
514         private CompactMap eventProperties = new CompactMap();
515         
516         public synchronized Object JavaDoc getProperty(Object JavaDoc key) {
517             return (eventProperties != null) ? eventProperties.get(key) : null;
518         }
519
520         @SuppressWarnings JavaDoc("unchecked")
521         public synchronized Object JavaDoc putProperty(Object JavaDoc key, Object JavaDoc value) {
522             return eventProperties.put(key, value);
523         }
524
525         @SuppressWarnings JavaDoc("unchecked")
526         public synchronized CompactMap.MapEntry putEntry(CompactMap.MapEntry entry) {
527             return eventProperties.putEntry(entry);
528         }
529
530         public int getIndex() {
531             return -1;
532         }
533
534         public Element JavaDoc getElement() {
535             return EventPropertiesElement.INSTANCE;
536         }
537
538         public Element JavaDoc[] getChildrenRemoved() {
539             return null;
540         }
541
542         public Element JavaDoc[] getChildrenAdded() {
543             return null;
544         }
545
546         public boolean replaceEdit(UndoableEdit JavaDoc anEdit) {
547             return false;
548         }
549
550         public boolean addEdit(UndoableEdit JavaDoc anEdit) {
551             return false;
552         }
553
554         public void undo() throws CannotUndoException JavaDoc {
555             // do nothing
556
}
557
558         public void redo() throws CannotRedoException JavaDoc {
559             // do nothing
560
}
561
562         public boolean isSignificant() {
563             return false;
564         }
565
566         public String JavaDoc getUndoPresentationName() {
567             return "";
568         }
569
570         public String JavaDoc getRedoPresentationName() {
571             return "";
572         }
573
574         public String JavaDoc getPresentationName() {
575             return "";
576         }
577
578         public void die() {
579             // do nothing
580
}
581
582         public boolean canUndo() {
583             return true;
584         }
585
586         public boolean canRedo() {
587             return true;
588         }
589
590     }
591     
592 }
593
Popular Tags