~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to editor/util/src/org/netbeans/lib/editor/util/swing/DocumentUtilities.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.lib.editor.util.swing;
 
43
 
 
44
import java.lang.reflect.Field;
 
45
import java.util.Map;
 
46
import javax.swing.event.DocumentEvent;
 
47
import javax.swing.event.DocumentListener;
 
48
import javax.swing.text.AbstractDocument;
 
49
import javax.swing.text.BadLocationException;
 
50
import javax.swing.text.Document;
 
51
import javax.swing.text.Element;
 
52
import javax.swing.text.Segment;
 
53
import javax.swing.text.StyledDocument;
 
54
import javax.swing.undo.CannotRedoException;
 
55
import javax.swing.undo.CannotUndoException;
 
56
import javax.swing.undo.UndoableEdit;
 
57
import org.netbeans.lib.editor.util.AbstractCharSequence;
 
58
import org.netbeans.lib.editor.util.CompactMap;
 
59
 
 
60
/**
 
61
 * Various utility methods related to swing text documents.
 
62
 *
 
63
 * @author Miloslav Metelka
 
64
 * @since 1.4
 
65
 */
 
66
 
 
67
public final class DocumentUtilities {
 
68
    
 
69
    private static final Object TYPING_MODIFICATION_DOCUMENT_PROPERTY = new Object();
 
70
    
 
71
    private static final Object TYPING_MODIFICATION_KEY = new Object();
 
72
    
 
73
    private static Field numReadersField;
 
74
    
 
75
    private static Field currWriterField;
 
76
    
 
77
    
 
78
    private DocumentUtilities() {
 
79
        // No instances
 
80
    }
 
81
 
 
82
    /**
 
83
     * Add document listener to document with given priority
 
84
     * or default to using regular {@link Document#addDocumentListener(DocumentListener)}
 
85
     * if the given document is not listener priority aware.
 
86
     * 
 
87
     * @param doc document to which the listener should be added.
 
88
     * @param listener document listener to add.
 
89
     * @param priority priority with which the listener should be added.
 
90
     *  If the document does not support document listeners ordering
 
91
     *  then the listener is added in a regular way by using
 
92
     *  {@link javax.swing.text.Document#addDocumentListener(
 
93
     *  javax.swing.event.DocumentListener)} method.
 
94
     */
 
95
    public static void addDocumentListener(Document doc, DocumentListener listener,
 
96
    DocumentListenerPriority priority) {
 
97
        if (!addPriorityDocumentListener(doc, listener, priority))
 
98
            doc.addDocumentListener(listener);
 
99
    }
 
100
    
 
101
    /**
 
102
     * Suitable for document implementations - adds document listener
 
103
     * to document with given priority and does not do anything
 
104
     * if the given document is not listener priority aware.
 
105
     * <br/>
 
106
     * Using this method in the document impls and defaulting
 
107
     * to super.addDocumentListener() in case it returns false
 
108
     * will ensure that there won't be an infinite loop in case the super constructors
 
109
     * would add some listeners prior initing of the priority listening.
 
110
     * 
 
111
     * @param doc document to which the listener should be added.
 
112
     * @param listener document listener to add.
 
113
     * @param priority priority with which the listener should be added.
 
114
     * @return true if the priority listener was added or false if the document
 
115
     *  does not support priority listening.
 
116
     */
 
117
    public static boolean addPriorityDocumentListener(Document doc, DocumentListener listener,
 
118
    DocumentListenerPriority priority) {
 
119
        PriorityDocumentListenerList priorityDocumentListenerList
 
120
                = (PriorityDocumentListenerList)doc.getProperty(PriorityDocumentListenerList.class);
 
121
        if (priorityDocumentListenerList != null) {
 
122
            priorityDocumentListenerList.add(listener, priority.getPriority());
 
123
            return true;
 
124
        } else
 
125
            return false;
 
126
    }
 
127
 
 
128
    /**
 
129
     * Remove document listener that was previously added to the document
 
130
     * with given priority or use default {@link Document#removeDocumentListener(DocumentListener)}
 
131
     * if the given document is not listener priority aware.
 
132
     * 
 
133
     * @param doc document from which the listener should be removed.
 
134
     * @param listener document listener to remove.
 
135
     * @param priority priority with which the listener should be removed.
 
136
     *  It should correspond to the priority with which the listener
 
137
     *  was added originally.
 
138
     */
 
139
    public static void removeDocumentListener(Document doc, DocumentListener listener,
 
140
    DocumentListenerPriority priority) {
 
141
        if (!removePriorityDocumentListener(doc, listener, priority))
 
142
            doc.removeDocumentListener(listener);
 
143
    }
 
144
 
 
145
    /**
 
146
     * Suitable for document implementations - removes document listener
 
147
     * from document with given priority and does not do anything
 
148
     * if the given document is not listener priority aware.
 
149
     * <br/>
 
150
     * Using this method in the document impls and defaulting
 
151
     * to super.removeDocumentListener() in case it returns false
 
152
     * will ensure that there won't be an infinite loop in case the super constructors
 
153
     * would remove some listeners prior initing of the priority listening.
 
154
     * 
 
155
     * @param doc document from which the listener should be removed.
 
156
     * @param listener document listener to remove.
 
157
     * @param priority priority with which the listener should be removed.
 
158
     * @return true if the priority listener was removed or false if the document
 
159
     *  does not support priority listening.
 
160
     */
 
161
    public static boolean removePriorityDocumentListener(Document doc, DocumentListener listener,
 
162
    DocumentListenerPriority priority) {
 
163
        PriorityDocumentListenerList priorityDocumentListenerList
 
164
                = (PriorityDocumentListenerList)doc.getProperty(PriorityDocumentListenerList.class);
 
165
        if (priorityDocumentListenerList != null) {
 
166
            priorityDocumentListenerList.remove(listener, priority.getPriority());
 
167
            return true;
 
168
        } else
 
169
            return false;
 
170
    }
 
171
 
 
172
    /**
 
173
     * This method should be used by swing document implementations that
 
174
     * want to support document listeners prioritization.
 
175
     * <br>
 
176
     * It should be called from document's constructor in the following way:<pre>
 
177
     *
 
178
     * class MyDocument extends AbstractDocument {
 
179
     *
 
180
     *     MyDocument() {
 
181
     *         super.addDocumentListener(DocumentUtilities.initPriorityListening(this));
 
182
     *     }
 
183
     *
 
184
     *     public void addDocumentListener(DocumentListener listener) {
 
185
     *         if (!DocumentUtilities.addDocumentListener(this, listener, DocumentListenerPriority.DEFAULT))
 
186
     *             super.addDocumentListener(listener);
 
187
     *     }
 
188
     *
 
189
     *     public void removeDocumentListener(DocumentListener listener) {
 
190
     *         if (!DocumentUtilities.removeDocumentListener(this, listener, DocumentListenerPriority.DEFAULT))
 
191
     *             super.removeDocumentListener(listener);
 
192
     *     }
 
193
     *
 
194
     * }</pre>
 
195
     *
 
196
     *
 
197
     * @param doc document to be initialized.
 
198
     * @return the document listener instance that should be added as a document
 
199
     *   listener typically by using <code>super.addDocumentListener()</code>
 
200
     *   in document's constructor.
 
201
     * @throws IllegalStateException when the document already has
 
202
     *   the property initialized.
 
203
     */
 
204
    public static DocumentListener initPriorityListening(Document doc) {
 
205
        if (doc.getProperty(PriorityDocumentListenerList.class) != null) {
 
206
            throw new IllegalStateException(
 
207
                    "PriorityDocumentListenerList already initialized for doc=" + doc); // NOI18N
 
208
        }
 
209
        PriorityDocumentListenerList listener = new PriorityDocumentListenerList();
 
210
        doc.putProperty(PriorityDocumentListenerList.class, listener);
 
211
        return listener;
 
212
    }
 
213
    
 
214
    /**
 
215
     * Get total count of document listeners attached to a particular document
 
216
     * (useful e.g. for logging).
 
217
     * <br/>
 
218
     * If the document uses priority listening then get the count of listeners
 
219
     * at all levels. If the document is not {@link AbstractDocument} the method
 
220
     * returns zero.
 
221
     * 
 
222
     * @param doc non-null document.
 
223
     * @return total count of document listeners attached to the document.
 
224
     */
 
225
    public static int getDocumentListenerCount(Document doc) {
 
226
        PriorityDocumentListenerList pdll;
 
227
        return (pdll = (PriorityDocumentListenerList)doc.getProperty(PriorityDocumentListenerList.class)) != null
 
228
                ? pdll.getListenerCount()
 
229
                : ((doc instanceof AbstractDocument)
 
230
                        ? ((AbstractDocument)doc).getListeners(DocumentListener.class).length
 
231
                        : 0);
 
232
    }
 
233
 
 
234
    /**
 
235
     * Mark that the ongoing document modification(s) will be caused
 
236
     * by user's typing.
 
237
     * It should be used by default-key-typed-action and the actions
 
238
     * for backspace and delete keys.
 
239
     * <br/>
 
240
     * The document listeners being fired may
 
241
     * query it by using {@link #isTypingModification(Document)}.
 
242
     * This method should always be used in the following pattern:
 
243
     * <pre>
 
244
     * DocumentUtilities.setTypingModification(doc, true);
 
245
     * try {
 
246
     *     doc.insertString(offset, typedText, null);
 
247
     * } finally {
 
248
     *    DocumentUtilities.setTypingModification(doc, false);
 
249
     * }
 
250
     * </pre>
 
251
     *
 
252
     * @see #isTypingModification(Document)
 
253
     */
 
254
    public static void setTypingModification(Document doc, boolean typingModification) {
 
255
        doc.putProperty(TYPING_MODIFICATION_DOCUMENT_PROPERTY, Boolean.valueOf(typingModification));
 
256
    }
 
257
    
 
258
    /**
 
259
     * This method should be used by document listeners to check whether
 
260
     * the just performed document modification was caused by user's typing.
 
261
     * <br/>
 
262
     * Certain functionality such as code completion or code templates
 
263
     * may benefit from that information. For example the java code completion
 
264
     * should only react to the typed "." but not if the same string was e.g.
 
265
     * pasted from the clipboard.
 
266
     *
 
267
     * @see #setTypingModification(Document, boolean)
 
268
     */
 
269
    public static boolean isTypingModification(Document doc) {
 
270
        Boolean b = (Boolean)doc.getProperty(TYPING_MODIFICATION_DOCUMENT_PROPERTY);
 
271
        return (b != null) ? b.booleanValue() : false;
 
272
    }
 
273
 
 
274
    /**
 
275
     * @deprecated
 
276
     * @see #isTypingModification(Document)
 
277
     */
 
278
    public static boolean isTypingModification(DocumentEvent evt) {
 
279
        return isTypingModification(evt.getDocument());
 
280
    }
 
281
 
 
282
    /**
 
283
     * Get text of the given document as char sequence.
 
284
     * <br>
 
285
     *
 
286
     * @param doc document for which the charsequence is being obtained.
 
287
     * @return non-null character sequence.
 
288
     *  <br>
 
289
     *  The returned character sequence should only be accessed under
 
290
     *  document's readlock (or writelock).
 
291
     */
 
292
    public static CharSequence getText(Document doc) {
 
293
        CharSequence text = (CharSequence)doc.getProperty(CharSequence.class);
 
294
        if (text == null) {
 
295
            text = new DocumentCharSequence(doc);
 
296
            doc.putProperty(CharSequence.class, text);
 
297
        }
 
298
        return text;
 
299
    }
 
300
    
 
301
    /**
 
302
     * Get a portion of text of the given document as char sequence.
 
303
     * <br>
 
304
     *
 
305
     * @param doc document for which the charsequence is being obtained.
 
306
     * @param offset starting offset of the charsequence to obtain.
 
307
     * @param length length of the charsequence to obtain
 
308
     * @return non-null character sequence.
 
309
     * @exception BadLocationException  some portion of the given range
 
310
     *   was not a valid part of the document.  The location in the exception
 
311
     *   is the first bad position encountered.
 
312
     *  <br>
 
313
     *  The returned character sequence should only be accessed under
 
314
     *  document's readlock (or writelock).
 
315
     */
 
316
    public static CharSequence getText(Document doc, int offset, int length) throws BadLocationException {
 
317
        CharSequence text = (CharSequence)doc.getProperty(CharSequence.class);
 
318
        if (text == null) {
 
319
            text = new DocumentCharSequence(doc);
 
320
            doc.putProperty(CharSequence.class, text);
 
321
        }
 
322
        try {
 
323
            return text.subSequence(offset, offset + length);
 
324
        } catch (IndexOutOfBoundsException e) {
 
325
            int badOffset = offset;
 
326
            if (offset >= 0 && offset + length > text.length()) {
 
327
                badOffset = length;
 
328
            }
 
329
            throw new BadLocationException(e.getMessage(), badOffset);
 
330
        }
 
331
    }
 
332
    
 
333
    /**
 
334
     * Document provider should call this method to allow for document event
 
335
     * properties being stored in document events.
 
336
     *
 
337
     * @param evt document event to which the storage should be added.
 
338
     *   It must be an undoable edit allowing to add an edit.
 
339
     */
 
340
    public static void addEventPropertyStorage(DocumentEvent evt) {
 
341
        // Parameter is DocumentEvent because it's more logical
 
342
        if (!(evt instanceof UndoableEdit)) {
 
343
            throw new IllegalStateException("evt not instanceof UndoableEdit: " + evt); // NOI18N
 
344
        }
 
345
        ((UndoableEdit)evt).addEdit(new EventPropertiesElementChange());
 
346
    }
 
347
    
 
348
    /**
 
349
     * Get a property of a given document event.
 
350
     *
 
351
     * @param evt non-null document event from which the property should be retrieved.
 
352
     * @param key non-null key of the property.
 
353
     * @return value for the given property.
 
354
     */
 
355
    public static Object getEventProperty(DocumentEvent evt, Object key) {
 
356
        EventPropertiesElementChange change = (EventPropertiesElementChange)
 
357
                evt.getChange(EventPropertiesElement.INSTANCE);
 
358
        return (change != null) ? change.getProperty(key) : null;
 
359
    }
 
360
    
 
361
    /**
 
362
     * Set a property of a given document event.
 
363
     *
 
364
     * @param evt non-null document event to which the property should be stored.
 
365
     * @param key non-null key of the property.
 
366
     * @param value for the given property.
 
367
     */
 
368
    public static void putEventProperty(DocumentEvent evt, Object key, Object value) {
 
369
        EventPropertiesElementChange change = (EventPropertiesElementChange)
 
370
                evt.getChange(EventPropertiesElement.INSTANCE);
 
371
        if (change == null) {
 
372
            throw new IllegalStateException("addEventPropertyStorage() not called for evt=" + evt); // NOI18N
 
373
        }
 
374
        change.putProperty(key, value);
 
375
    }
 
376
    
 
377
    /**
 
378
     * Set a property of a given document event by using the given map entry.
 
379
     * <br/>
 
380
     * The present implementation is able to directly store instances
 
381
     * of <code>CompactMap.MapEntry</code>. Other map entry implementations
 
382
     * will be delegated to {@link #putEventProperty(DocumentEvent, Object, Object)}.
 
383
     *
 
384
     * @param evt non-null document event to which the property should be stored.
 
385
     * @param mapEntry non-null map entry which should be stored.
 
386
     *  Generally after this method finishes the {@link #getEventProperty(DocumentEvent, Object)}
 
387
     *  will return <code>mapEntry.getValue()</code> for <code>mapEntry.getKey()</code> key.
 
388
     */
 
389
    public static void putEventProperty(DocumentEvent evt, Map.Entry mapEntry) {
 
390
        if (mapEntry instanceof CompactMap.MapEntry) {
 
391
            EventPropertiesElementChange change = (EventPropertiesElementChange)
 
392
                    evt.getChange(EventPropertiesElement.INSTANCE);
 
393
            if (change == null) {
 
394
                throw new IllegalStateException("addEventPropertyStorage() not called for evt=" + evt); // NOI18N
 
395
            }
 
396
            change.putEntry((CompactMap.MapEntry)mapEntry);
 
397
 
 
398
        } else {
 
399
            putEventProperty(evt, mapEntry.getKey(), mapEntry.getValue());
 
400
        }
 
401
    }
 
402
    
 
403
    /**
 
404
     * Fix the given offset according to the performed modification.
 
405
     * 
 
406
     * @param offset >=0 offset in a document.
 
407
     * @param evt document event describing change in the document.
 
408
     * @return offset updated by applying the document change to the offset.
 
409
     */
 
410
    public static int fixOffset(int offset, DocumentEvent evt) {
 
411
        int modOffset = evt.getOffset();
 
412
        if (evt.getType() == DocumentEvent.EventType.INSERT) {
 
413
            if (offset >= modOffset) {
 
414
                offset += evt.getLength();
 
415
            }
 
416
        } else if (evt.getType() == DocumentEvent.EventType.REMOVE) {
 
417
            if (offset > modOffset) {
 
418
                offset = Math.min(offset - evt.getLength(), modOffset);
 
419
            }
 
420
        }
 
421
        return offset;
 
422
    }
 
423
    
 
424
    /**
 
425
     * Get text of the given document modification.
 
426
     * <br/>
 
427
     * It's implemented as retrieving of a <code>String.class</code>.
 
428
     *
 
429
     * @param evt document event describing either document insertion or removal
 
430
     *  (change event type events will produce null result).
 
431
     * @return text that was inserted/removed from the document by the given
 
432
     *  document modification or null if that information is not provided
 
433
     *  by that document event.
 
434
     */
 
435
    public static String getModificationText(DocumentEvent evt) {
 
436
        return (String)getEventProperty(evt, String.class);
 
437
    }
 
438
    
 
439
    /**
 
440
     * Check whether the given document is read-locked by at least one thread
 
441
     * or whether it was write-locked by the current thread (write-locking
 
442
     * grants the read-access automatically).
 
443
     * <br/>
 
444
     * The method currently only works for {@link javax.swing.text.AbstractDocument}
 
445
     * based documents and it uses reflection.
 
446
     * <br/>
 
447
     * Unfortunately the AbstractDocument only records number of read-lockers
 
448
     * but not the thread references that performed the read-locking. Thus it can't be verified
 
449
     * whether current thread has performed read locking or another thread.
 
450
     * 
 
451
     * @param doc non-null document instance.
 
452
     * @return true if the document was read-locked by some thread
 
453
     *   or false if not (or if doc not-instanceof AbstractDocument).
 
454
     * @since 1.17
 
455
     */
 
456
    public static boolean isReadLocked(Document doc) {
 
457
        if (checkAbstractDoc(doc)) {
 
458
            if (isWriteLocked(doc))
 
459
                return true;
 
460
            if (numReadersField == null) {
 
461
                try {
 
462
                    numReadersField = AbstractDocument.class.getDeclaredField("numReaders");
 
463
                } catch (NoSuchFieldException ex) {
 
464
                    throw new IllegalStateException(ex);
 
465
                }
 
466
                numReadersField.setAccessible(true);
 
467
            }
 
468
            try {
 
469
                synchronized (doc) {
 
470
                    return numReadersField.getInt(doc) > 0;
 
471
                }
 
472
            } catch (IllegalAccessException ex) {
 
473
                throw new IllegalStateException(ex);
 
474
            }
 
475
        }
 
476
        return false;
 
477
    }
 
478
    
 
479
    /**
 
480
     * Check whether the given document is write-locked by the current thread.
 
481
     * <br/>
 
482
     * The method currently only works for {@link javax.swing.text.AbstractDocument}
 
483
     * based documents and it uses reflection.
 
484
     * 
 
485
     * @param doc non-null document instance.
 
486
     * @return true if the document was write-locked by the current thread
 
487
     *   or false if not (or if doc not-instanceof AbstractDocument).
 
488
     * @since 1.17
 
489
     */
 
490
    public static boolean isWriteLocked(Document doc) {
 
491
        if (checkAbstractDoc(doc)) {
 
492
            if (currWriterField == null) {
 
493
                try {
 
494
                    currWriterField = AbstractDocument.class.getDeclaredField("currWriter");
 
495
                } catch (NoSuchFieldException ex) {
 
496
                    throw new IllegalStateException(ex);
 
497
                }
 
498
                currWriterField.setAccessible(true);
 
499
            }
 
500
            try {
 
501
                synchronized (doc) {
 
502
                    return currWriterField.get(doc) == Thread.currentThread();
 
503
                }
 
504
            } catch (IllegalAccessException ex) {
 
505
                throw new IllegalStateException(ex);
 
506
            }
 
507
        }
 
508
        return false; // not AbstractDocument
 
509
    }
 
510
    
 
511
    private static boolean checkAbstractDoc(Document doc) {
 
512
        if (doc == null)
 
513
            throw new IllegalArgumentException("document is null");
 
514
        return (doc instanceof AbstractDocument);
 
515
    }
 
516
    
 
517
    /**
 
518
     * Get the paragraph element for the given document.
 
519
     *
 
520
     * @param doc non-null document instance.
 
521
     * @param offset offset in the document >=0
 
522
     * @return paragraph element containing the given offset.
 
523
     */
 
524
    public static Element getParagraphElement(Document doc, int offset) {
 
525
        Element paragraph;
 
526
        if (doc instanceof StyledDocument) {
 
527
            paragraph = ((StyledDocument)doc).getParagraphElement(offset);
 
528
        } else {
 
529
            Element rootElem = doc.getDefaultRootElement();
 
530
            int index = rootElem.getElementIndex(offset);
 
531
            paragraph = rootElem.getElement(index);
 
532
            if ((offset < paragraph.getStartOffset()) || (offset >= paragraph.getEndOffset())) {
 
533
                paragraph = null;
 
534
            }
 
535
        }
 
536
        return paragraph;
 
537
    }
 
538
    
 
539
    /**
 
540
     * Get the root of the paragraph elements for the given document.
 
541
     *
 
542
     * @param doc non-null document instance.
 
543
     * @return root element of the paragraph elements.
 
544
     */
 
545
    public static Element getParagraphRootElement(Document doc) {
 
546
        if (doc instanceof StyledDocument) {
 
547
            return ((StyledDocument)doc).getParagraphElement(0).getParentElement();
 
548
        } else {
 
549
            return doc.getDefaultRootElement().getElement(0).getParentElement();
 
550
        }
 
551
    }
 
552
 
 
553
    /**
 
554
     * Implementation of the character sequence for a generic document
 
555
     * that does not provide its own implementation of character sequence.
 
556
     */
 
557
    private static final class DocumentCharSequence extends AbstractCharSequence.StringLike {
 
558
        
 
559
        private final Segment segment = new Segment();
 
560
        
 
561
        private final Document doc;
 
562
        
 
563
        DocumentCharSequence(Document doc) {
 
564
            this.doc = doc;
 
565
        }
 
566
 
 
567
        public int length() {
 
568
            return doc.getLength();
 
569
        }
 
570
 
 
571
        public synchronized char charAt(int index) {
 
572
            try {
 
573
                doc.getText(index, 1, segment);
 
574
            } catch (BadLocationException e) {
 
575
                throw new IndexOutOfBoundsException(e.getMessage()
 
576
                    + " at offset=" + e.offsetRequested()); // NOI18N
 
577
            }
 
578
            char ch = segment.array[segment.offset];
 
579
            segment.array = null; // Allow GC of large char arrays
 
580
            return ch;
 
581
        }
 
582
 
 
583
    }
 
584
    
 
585
    /**
 
586
     * Helper element used as a key in searching for an element change
 
587
     * being a storage of the additional properties in a document event.
 
588
     */
 
589
    private static final class EventPropertiesElement implements Element {
 
590
        
 
591
        static final EventPropertiesElement INSTANCE = new EventPropertiesElement();
 
592
        
 
593
        public int getStartOffset() {
 
594
            return 0;
 
595
        }
 
596
 
 
597
        public int getEndOffset() {
 
598
            return 0;
 
599
        }
 
600
 
 
601
        public int getElementCount() {
 
602
            return 0;
 
603
        }
 
604
 
 
605
        public int getElementIndex(int offset) {
 
606
            return -1;
 
607
        }
 
608
 
 
609
        public Element getElement(int index) {
 
610
            return null;
 
611
        }
 
612
 
 
613
        public boolean isLeaf() {
 
614
            return true;
 
615
        }
 
616
 
 
617
        public Element getParentElement() {
 
618
            return null;
 
619
        }
 
620
 
 
621
        public String getName() {
 
622
            return "Helper element for modification text providing"; // NOI18N
 
623
        }
 
624
 
 
625
        public Document getDocument() {
 
626
            return null;
 
627
        }
 
628
 
 
629
        public javax.swing.text.AttributeSet getAttributes() {
 
630
            return null;
 
631
        }
 
632
        
 
633
        public String toString() {
 
634
            return getName();
 
635
        }
 
636
 
 
637
    }
 
638
    
 
639
    private static final class EventPropertiesElementChange
 
640
    implements DocumentEvent.ElementChange, UndoableEdit  {
 
641
        
 
642
        private CompactMap eventProperties = new CompactMap();
 
643
        
 
644
        public synchronized Object getProperty(Object key) {
 
645
            return (eventProperties != null) ? eventProperties.get(key) : null;
 
646
        }
 
647
 
 
648
        @SuppressWarnings("unchecked")
 
649
        public synchronized Object putProperty(Object key, Object value) {
 
650
            return eventProperties.put(key, value);
 
651
        }
 
652
 
 
653
        @SuppressWarnings("unchecked")
 
654
        public synchronized CompactMap.MapEntry putEntry(CompactMap.MapEntry entry) {
 
655
            return eventProperties.putEntry(entry);
 
656
        }
 
657
 
 
658
        public int getIndex() {
 
659
            return -1;
 
660
        }
 
661
 
 
662
        public Element getElement() {
 
663
            return EventPropertiesElement.INSTANCE;
 
664
        }
 
665
 
 
666
        public Element[] getChildrenRemoved() {
 
667
            return null;
 
668
        }
 
669
 
 
670
        public Element[] getChildrenAdded() {
 
671
            return null;
 
672
        }
 
673
 
 
674
        public boolean replaceEdit(UndoableEdit anEdit) {
 
675
            return false;
 
676
        }
 
677
 
 
678
        public boolean addEdit(UndoableEdit anEdit) {
 
679
            return false;
 
680
        }
 
681
 
 
682
        public void undo() throws CannotUndoException {
 
683
            // do nothing
 
684
        }
 
685
 
 
686
        public void redo() throws CannotRedoException {
 
687
            // do nothing
 
688
        }
 
689
 
 
690
        public boolean isSignificant() {
 
691
            return false;
 
692
        }
 
693
 
 
694
        public String getUndoPresentationName() {
 
695
            return "";
 
696
        }
 
697
 
 
698
        public String getRedoPresentationName() {
 
699
            return "";
 
700
        }
 
701
 
 
702
        public String getPresentationName() {
 
703
            return "";
 
704
        }
 
705
 
 
706
        public void die() {
 
707
            // do nothing
 
708
        }
 
709
 
 
710
        public boolean canUndo() {
 
711
            return true;
 
712
        }
 
713
 
 
714
        public boolean canRedo() {
 
715
            return true;
 
716
        }
 
717
 
 
718
    }
 
719
    
 
720
}