KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > text > CloneableEditorSupport


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-2007 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.openide.text;
20
21 import java.awt.Component JavaDoc;
22 import java.util.logging.Level JavaDoc;
23 import java.util.logging.Logger JavaDoc;
24 import org.openide.DialogDisplayer;
25 import org.openide.NotifyDescriptor;
26 import org.openide.awt.StatusDisplayer;
27 import org.openide.awt.UndoRedo;
28 import org.openide.cookies.EditorCookie;
29 import org.openide.util.Lookup;
30 import org.openide.util.NbBundle;
31
32 //import org.openide.util.actions.SystemAction;
33
import org.openide.util.RequestProcessor;
34 import org.openide.util.Task;
35 import org.openide.util.TaskListener;
36 import org.openide.util.UserQuestionException;
37 import org.openide.windows.*;
38
39 import java.awt.Font JavaDoc;
40 import java.awt.Toolkit JavaDoc;
41 import java.awt.print.PageFormat JavaDoc;
42 import java.awt.print.Pageable JavaDoc;
43 import java.awt.print.Printable JavaDoc;
44 import java.awt.print.PrinterAbortException JavaDoc;
45 import java.awt.print.PrinterException JavaDoc;
46 import java.awt.print.PrinterJob JavaDoc;
47
48 import java.beans.PropertyChangeEvent JavaDoc;
49 import java.beans.PropertyChangeListener JavaDoc;
50 import java.beans.PropertyChangeSupport JavaDoc;
51
52 import java.io.*;
53 import java.lang.ref.Reference JavaDoc;
54 import java.lang.ref.WeakReference JavaDoc;
55
56 import java.util.*;
57
58 import javax.swing.JButton JavaDoc;
59 import javax.swing.JEditorPane JavaDoc;
60 import javax.swing.SwingUtilities JavaDoc;
61 import javax.swing.event.ChangeEvent JavaDoc;
62 import javax.swing.event.ChangeListener JavaDoc;
63 import javax.swing.event.DocumentEvent JavaDoc;
64 import javax.swing.event.DocumentListener JavaDoc;
65 import javax.swing.event.UndoableEditEvent JavaDoc;
66 import javax.swing.text.*;
67 import javax.swing.undo.CannotRedoException JavaDoc;
68 import javax.swing.undo.CannotUndoException JavaDoc;
69 import javax.swing.undo.UndoableEdit JavaDoc;
70 import org.netbeans.api.editor.mimelookup.MimeLookup;
71 import org.netbeans.api.editor.mimelookup.MimePath;
72 import org.openide.util.Exceptions;
73
74
75 /** Support for associating an editor and a Swing {@link Document}.
76 * Can be assigned as a cookie to any editable data object.
77 * This class is abstract, so any subclass has to provide implementation
78 * for abstract method (usually for generating of messages) and also
79 * provide environment {@link Env} to give this support access to
80 * input/output streams, mime type and other features of edited object.
81 *
82 * <P>
83 * This class implements methods of the interfaces
84 * {@link org.openide.cookies.EditorCookie}, {@link org.openide.cookies.OpenCookie},
85 * {@link org.openide.cookies.EditCookie},
86 * {@link org.openide.cookies.ViewCookie}, {@link org.openide.cookies.LineCookie},
87 * {@link org.openide.cookies.CloseCookie}, and {@link org.openide.cookies.PrintCookie}
88 * but does not implement
89 * those interfaces. It is up to the subclass to decide which interfaces
90 * really implement and which not.
91 *
92 * @author Jaroslav Tulach
93 */

94 public abstract class CloneableEditorSupport extends CloneableOpenSupport {
95     /** Common name for editor mode. */
96     public static final String JavaDoc EDITOR_MODE = "editor"; // NOI18N
97
private static final String JavaDoc PROP_PANE = "CloneableEditorSupport.Pane"; //NOI18N
98
private static final int DOCUMENT_NO = 0;
99     private static final int DOCUMENT_LOADING = 1;
100     private static final int DOCUMENT_READY = 2;
101     private static final int DOCUMENT_RELOADING = 3;
102
103     /** Used for allowing to pass getDocument method
104      * when called from loadDocument. */

105     private static final ThreadLocal JavaDoc<Boolean JavaDoc> LOCAL_LOAD_TASK = new ThreadLocal JavaDoc<Boolean JavaDoc>();
106
107     /** error manager for CloneableEditorSupport logging and error reporting */
108     private static final Logger JavaDoc ERR = Logger.getLogger("org.openide.text.CloneableEditorSupport"); // NOI18N
109

110     /** Flag saying if the CloneableEditorSupport handles already the UserQuestionException*/
111     private boolean inUserQuestionExceptionHandler;
112
113     /** Task for preparing the document. Consists for loading a document,
114     * firing </code>stateChange</code> and
115     * initializing it by attaching listeners listening to document changes, such as SavingManager and
116     * LineSet.
117     */

118     private Task prepareTask;
119
120     /** editor kit to work with */
121     private EditorKit kit;
122
123     /** document we work with */
124     private StyledDocument doc;
125
126     /** Non default MIME type used to editing */
127     private String JavaDoc mimeType;
128
129     /** Actions to show in toolbar */
130
131     // private SystemAction[] actions;
132

133     /** Listener to the document changes and all other changes */
134     private Listener listener;
135
136     /** the undo/redo manager to use for this document */
137     private UndoRedo.Manager undoRedo;
138
139     /** lines set for this object */
140     private Line.Set lineSet;
141
142     /** Helper variable to prevent multiple cocurrent printing of this
143      * instance. */

144     private boolean printing;
145
146     /** Lock used for access to <code>printing</code> variable. */
147     private final Object JavaDoc LOCK_PRINTING = new Object JavaDoc();
148
149     /** position manager */
150     private PositionRef.Manager positionManager;
151
152     /** The string which will be appended to the name of top component
153     * when top component becomes modified */

154
155     // protected String modifiedAppendix = " *"; // NOI18N
156

157     /** Listeners for the changing of the state - document in memory X closed. */
158     private Set<ChangeListener JavaDoc> listeners;
159
160     /** last selected editor pane. */
161     private transient Reference JavaDoc<Pane JavaDoc> lastSelected;
162
163     /** The time of the last save to determine the real external modifications */
164     private long lastSaveTime;
165
166     /** Whether the reload dialog is currently opened. Prevents poping of multiple
167      * reload dialogs if there is more external saves.
168      */

169     private boolean reloadDialogOpened;
170
171     /** Support for property change listeners*/
172     private PropertyChangeSupport JavaDoc propertyChangeSupport;
173
174     /** context of this editor support */
175     private Lookup lookup;
176
177     /** Flag whether the document is already modified or not.*/
178
179     // #34728 performance optimization
180
private boolean alreadyModified;
181
182     /**
183      * Whether previous or upcoming undo is being undone
184      * once the notifyModified() is prohibited.
185      * <br>
186      * Also set when document is being reloaded.
187      */

188     private boolean revertingUndoOrReloading;
189     private boolean justRevertedToNotModified;
190     private int documentStatus = DOCUMENT_NO;
191     private Throwable JavaDoc prepareDocumentRuntimeException;
192
193     /** Reference to WeakHashMap that is used by all Line.Sets created
194      * for this CloneableEditorSupport.
195      */

196     private Map<Line,Reference JavaDoc<Line>> lineSetWHM;
197     private boolean annotationsLoaded;
198
199     /* Whether the file was externally modified or not.
200      * This flag is used only in saveDocument to prevent
201      * overriding of externally modified file. See issue #32777.
202      */

203     private boolean externallyModified;
204
205     /** Creates new CloneableEditorSupport attached to given environment.
206     *
207     * @param env environment that is source of all actions around the
208     * data object
209     */

210     public CloneableEditorSupport(Env env) {
211         this(env, Lookup.EMPTY);
212     }
213
214     /** Creates new CloneableEditorSupport attached to given environment.
215     *
216     * @param env environment that is source of all actions around the
217     * data object
218     * @param l the context that will be passed to each Line produced
219     * by this support's Line.Set. The line will return it from Line.getLookup
220     * call
221     */

222     public CloneableEditorSupport(Env env, Lookup l) {
223         super(env);
224         this.lookup = l;
225     }
226
227     //
228
// abstract messages section
229
//
230

231     /** Constructs message that should be displayed when the data object
232     * is modified and is being closed.
233     *
234     * @return text to show to the user
235     */

236     protected abstract String JavaDoc messageSave();
237
238     /** Constructs message that should be used to name the editor component.
239     *
240     * @return name of the editor
241     */

242     protected abstract String JavaDoc messageName();
243     
244     /** Constructs message that should be used to name the editor component
245      * in html fashion, with possible coloring, text styles etc.
246      *
247      * May return null if no html name is needed or available.
248      *
249      * @return html name of the editor component or null
250      * @since 6.8
251      */

252     protected String JavaDoc messageHtmlName() {
253         return null;
254     }
255
256     /** Constructs the ID used for persistence of opened editors.
257      * Should be overridden to return sane ID of the underlying document,
258      * like the name of the disk file.
259      *
260      * @return ID of the document
261      * @since 4.24
262      */

263     protected String JavaDoc documentID() {
264         return messageName();
265     }
266
267     /** Text to use as tooltip for component.
268      *
269      * @return text to show to the user
270      */

271     protected abstract String JavaDoc messageToolTip();
272
273     /** Computes display name for a line produced
274      * by this {@link CloneableEditorSupport#getLineSet }. The default
275      * implementation reuses messageName and line number of the line.
276      *
277      * @param line the line object to compute display name for
278      * @return display name for the line like "MyFile.java:243"
279      *
280      * @since 4.3
281      */

282     protected String JavaDoc messageLine(Line line) {
283         return NbBundle.getMessage(Line.class, "FMT_CESLineDisplayName", messageName(), line.getLineNumber() + 1);
284     }
285
286     //
287
// Section of getter of default objects
288
//
289

290     /** Getter for the environment that was provided in the constructor.
291     * @return the environment
292     */

293     final Env cesEnv() {
294         return (Env) env;
295     }
296
297     /** Getter for the kit that loaded the document.
298     */

299     final EditorKit cesKit() {
300         return kit;
301     }
302
303     /**
304      * Gets the undo redo manager.
305      * @return the manager
306      */

307     protected final synchronized UndoRedo.Manager getUndoRedo() {
308         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
309         if (redirect != null) {
310             return redirect.getUndoRedo();
311         }
312         
313         if (undoRedo == null) {
314             undoRedo = createUndoRedoManager();
315         }
316
317         return undoRedo;
318     }
319
320     /** Provides access to position manager for the document.
321     * It maintains a set of positions even the document is in memory
322     * or is on the disk.
323     *
324     * @return position manager
325     */

326     final synchronized PositionRef.Manager getPositionManager() {
327         if (positionManager == null) {
328             positionManager = new PositionRef.Manager(this);
329         }
330
331         return positionManager;
332     }
333
334     void ensureAnnotationsLoaded() {
335         if (!annotationsLoaded) {
336             annotationsLoaded = true;
337
338             Line.Set lines = getLineSet();
339             for (AnnotationProvider act : Lookup.getDefault().lookupAll(AnnotationProvider.class)) {
340                 act.annotate(lines, lookup);
341             }
342         }
343     }
344
345     /** When openning of a document fails with an UserQuestionException
346      * this is the method that is supposed to handle the communication.
347      */

348     private void askUserAndDoOpen(UserQuestionException e) {
349         while (e != null) {
350             NotifyDescriptor nd = new NotifyDescriptor.Confirmation(
351                     e.getLocalizedMessage(), NotifyDescriptor.YES_NO_OPTION
352                 );
353             nd.setOptions(new Object JavaDoc[] { NotifyDescriptor.YES_OPTION, NotifyDescriptor.NO_OPTION });
354
355             Object JavaDoc res = DialogDisplayer.getDefault().notify(nd);
356
357             if (NotifyDescriptor.OK_OPTION.equals(res)) {
358                 try {
359                     e.confirmed();
360                 } catch (IOException ex1) {
361                     Exceptions.printStackTrace(ex1);
362
363                     return;
364                 }
365             } else {
366                 return;
367             }
368
369             e = null;
370
371             try {
372                 getListener().loadExc = null;
373                 prepareTask = null;
374                 documentStatus = DOCUMENT_NO;
375                 openDocument();
376
377                 super.open();
378             } catch (UserQuestionException ex) {
379                 e = ex;
380             } catch (IOException ex) {
381                 ERR.log(Level.INFO, null, ex);
382             }
383         }
384     }
385
386     /** Overrides superclass method, first processes document preparation.
387      * @see #prepareDocument */

388     public void open() {
389         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
390         if (redirect != null) {
391             redirect.open();
392             return;
393         }
394         
395         try {
396             if (getListener().loadExc instanceof UserQuestionException) {
397                 getListener().loadExc = null;
398                 prepareTask = null;
399                 documentStatus = DOCUMENT_NO;
400             }
401
402             openDocument();
403             super.open();
404         } catch (final UserQuestionException e) {
405             if (SwingUtilities.isEventDispatchThread()) {
406                 askUserAndDoOpen(e);
407             } else {
408                 SwingUtilities.invokeLater(
409                     new Runnable JavaDoc() {
410                         public void run() {
411                             askUserAndDoOpen(e);
412                         }
413                     }
414                 );
415             }
416         } catch (IOException e) {
417             ERR.log(Level.INFO, null, e);
418         }
419     }
420
421     //
422
// EditorCookie.Observable implementation
423
//
424

425     /** Add a PropertyChangeListener to the listener list.
426      * See {@link org.openide.cookies.EditorCookie.Observable}.
427      * @param l the PropertyChangeListener to be added
428      * @since 3.40
429      */

430     public final void addPropertyChangeListener(java.beans.PropertyChangeListener JavaDoc l) {
431         getPropertyChangeSupport().addPropertyChangeListener(l);
432     }
433
434     /** Remove a PropertyChangeListener from the listener list.
435      * See {@link org.openide.cookies.EditorCookie.Observable}.
436      * @param l the PropertyChangeListener to be removed
437      * @since 3.40
438      */

439     public final void removePropertyChangeListener(java.beans.PropertyChangeListener JavaDoc l) {
440         getPropertyChangeSupport().removePropertyChangeListener(l);
441     }
442
443     /** Report a bound property update to any registered listeners.
444      * @param propertyName the programmatic name of the property that was changed.
445      * @param oldValue rhe old value of the property.
446      * @param newValue the new value of the property.
447      * @since 3.40
448      */

449     protected final void firePropertyChange(String JavaDoc propertyName, Object JavaDoc oldValue, Object JavaDoc newValue) {
450         getPropertyChangeSupport().firePropertyChange(propertyName, oldValue, newValue);
451     }
452
453     private synchronized PropertyChangeSupport JavaDoc getPropertyChangeSupport() {
454         if (propertyChangeSupport == null) {
455             propertyChangeSupport = new PropertyChangeSupport JavaDoc(this);
456         }
457
458         return propertyChangeSupport;
459     }
460
461     //
462
// EditorCookie implementation
463
//
464
// editor cookie .......................................................................
465

466     /** Load the document into memory. This is done
467     * in different thread. A task for the thread is returned
468     * so anyone may test whether the loading has been finished
469     * or is still in process.
470     *
471     * @return task for control over loading
472     */

473     public Task prepareDocument() {
474         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
475         if (redirect != null) {
476             return redirect.prepareDocument();
477         }
478         synchronized (getLock()) {
479             switch (documentStatus) {
480             case DOCUMENT_NO:
481                 documentStatus = DOCUMENT_LOADING;
482
483                 return prepareDocument(false);
484
485             default:
486
487                 if (prepareTask == null) { // should never happen
488
throw new IllegalStateException JavaDoc();
489                 }
490
491                 return prepareTask;
492             }
493         }
494     }
495
496     /** @param clearDocument indicates whether the document is needed
497      * to clear before (used for reloading) */

498     private Task prepareDocument(final boolean notUsed) {
499         if (prepareTask != null) {
500             return prepareTask;
501         }
502
503         try {
504             // listen to modifications on env, but remove
505
// previous instance first
506
env.removePropertyChangeListener(getListener());
507             env.addPropertyChangeListener(getListener());
508
509             // after call to this method the originalDoc and kit are initialized
510
// in spite of that the document is not yet fully read in
511
kit = createEditorKit();
512
513             if (doc == null) {
514                 doc = createStyledDocument(kit);
515
516                 // here would be the testability hook for issue 56413
517
// (Deadlock56413Test), but I use the reflection in the test
518
// instead, so the test depends on the above assignment
519
}
520
521             final StyledDocument docToLoad = doc;
522
523             // The thread nume should be: "Loading document " + env; // NOI18N
524
prepareTask = RequestProcessor.getDefault().post(new Runnable JavaDoc() {
525                                                    private boolean runningInAtomicLock;
526
527                                                    public void run() {
528                                                        // Run the operations under atomic lock primarily due
529
// to reload which occurs in a widely published document instance
530
// where another threads may operate already
531
if (!runningInAtomicLock) {
532                                                            runningInAtomicLock = true;
533                                                            NbDocument.runAtomic(docToLoad,
534                                                                                 this);
535                                                            return;
536                                                        }
537                                                        // Prevent operating on top of no longer active document
538
synchronized (getLock()) {
539                                                            if (documentStatus ==
540                                                                DOCUMENT_NO) {
541                                                                return;
542                                                            }
543                                                            // Check whether the document to be loaded was not closed
544
if (doc != docToLoad) {
545                                                                return;
546                                                            }
547                                                            prepareDocumentRuntimeException = null;
548                                                            int targetStatus = DOCUMENT_NO;
549
550                                                            try {
551                                                                // uses the listener's run method to initialize whole document
552
getListener().run();
553                                                                // assign before fireDocumentChange() as listener should be able to access getDocument()
554
documentStatus = DOCUMENT_READY;
555                                                                fireDocumentChange(doc,
556                                                                                   false);
557                                                                // Confirm that whole loading succeeded
558
targetStatus = DOCUMENT_READY;
559                                                                // Add undoable listener when all work in
560
// atomic action has finished
561
// definitively sooner than leaving lock section
562
// and notifying al waiters, see #47022
563
doc.addUndoableEditListener(getUndoRedo());
564                                                            }
565                                                            catch (DelegateIOExc t) {
566                                                                prepareDocumentRuntimeException = t;
567                                                                prepareTask = null;
568                                                            }
569                                                            catch (RuntimeException JavaDoc t) {
570                                                                prepareDocumentRuntimeException = t;
571                                                                prepareTask = null;
572                                                                Exceptions.printStackTrace(t);
573                                                                throw t;
574                                                            }
575                                                            catch (Error JavaDoc t) {
576                                                                prepareDocumentRuntimeException = t;
577                                                                prepareTask = null;
578                                                                Exceptions.printStackTrace(t);
579                                                                throw t;
580                                                            }
581                                                            finally {
582                                                                synchronized (getLock()) {
583                                                                    documentStatus = targetStatus;
584                                                                    getLock().notifyAll();
585                                                                }
586                                                            }
587                                                        }
588                                                    }
589                                                });
590         } catch (RuntimeException JavaDoc ex) {
591             synchronized (getLock()) {
592                 prepareDocumentRuntimeException = ex;
593                 documentStatus = DOCUMENT_NO;
594                 getLock().notifyAll();
595             }
596
597             throw ex;
598         }
599
600         return prepareTask;
601     }
602
603     final void addRemoveDocListener(Document d, boolean add) {
604         if (Boolean.TRUE.equals(d.getProperty("supportsModificationListener"))) { // NOI18N
605

606             if (add) {
607                 d.putProperty("modificationListener", getListener()); // NOI18N
608
} else {
609                 d.putProperty("modificationListener", null); // NOI18N
610
}
611         } else {
612             if (add) {
613                 d.addDocumentListener(getListener());
614             } else {
615                 d.removeDocumentListener(getListener());
616             }
617         }
618     }
619
620     /** Clears the <code>doc</code> document. Helper method. */
621     private void clearDocument() {
622         NbDocument.runAtomic(
623             doc,
624             new Runnable JavaDoc() {
625                 public void run() {
626                     try {
627                         addRemoveDocListener(doc, false);
628                         doc.remove(0, doc.getLength()); // remove all text
629
addRemoveDocListener(doc, true);
630                     } catch (BadLocationException ble) {
631                         ERR.log(Level.INFO, null, ble);
632                     }
633                 }
634             }
635         );
636     }
637
638     /** Get the document associated with this cookie.
639     * It is an instance of Swing's {@link StyledDocument} but it should
640     * also understand the NetBeans {@link NbDocument#GUARDED} to
641     * prevent certain lines from being edited by the user.
642     * <P>
643     * If the document is not loaded the method blocks until
644     * it is.
645     *
646     * @return the styled document for this cookie that
647     * understands the guarded attribute
648     * @exception IOException if the document could not be loaded
649     */

650     public StyledDocument openDocument() throws IOException {
651         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
652         if (redirect != null) {
653             return redirect.openDocument();
654         }
655         synchronized (getLock()) {
656             return openDocumentCheckIOE();
657         }
658     }
659
660     private StyledDocument openDocumentCheckIOE() throws IOException {
661         StyledDocument doc = openDocumentImpl();
662
663         IOException ioe = getListener().checkLoadException();
664
665         if (ioe != null) {
666             throw ioe;
667         }
668
669         return doc;
670     }
671
672     /**
673      * Must be called under getLock().
674      */

675     private StyledDocument openDocumentImpl() throws IOException, InterruptedIOException {
676         switch (documentStatus) {
677         case DOCUMENT_NO:
678             documentStatus = DOCUMENT_LOADING;
679             prepareDocument(false);
680
681             return openDocumentImpl();
682
683         case DOCUMENT_RELOADING: // proceed to DOCUMENT_READY
684
case DOCUMENT_READY:
685             return doc;
686
687         default: // loading
688

689             try {
690                 getLock().wait();
691             } catch (InterruptedException JavaDoc e) {
692                 throw (InterruptedIOException) new InterruptedIOException().initCause(e);
693             }
694
695             if (prepareDocumentRuntimeException != null) {
696                 if (prepareDocumentRuntimeException instanceof DelegateIOExc) {
697                     throw (IOException) prepareDocumentRuntimeException.getCause();
698                 }
699
700                 if (prepareDocumentRuntimeException instanceof Error JavaDoc) {
701                     throw (Error JavaDoc) prepareDocumentRuntimeException;
702                 } else {
703                     throw (RuntimeException JavaDoc) prepareDocumentRuntimeException;
704                 }
705             }
706
707             return openDocumentImpl();
708         }
709     }
710
711     /** Get the document. This method may be called before the document initialization
712      * (<code>prepareTask</code>)
713      * has been completed, in such a case the document must not be modified.
714      * @return document or <code>null</code> if it is not yet loaded
715      */

716     public StyledDocument getDocument() {
717         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
718         if (redirect != null) {
719             return redirect.getDocument();
720         }
721         synchronized (getLock()) {
722             while (true) {
723                 switch (documentStatus) {
724                 case DOCUMENT_NO:
725                     return null;
726
727                 default: // ready, loading or reloading
728

729                     if (LOCAL_LOAD_TASK.get() != null) {
730                         return doc;
731                     }
732
733                     try {
734                         return openDocumentCheckIOE();
735                     } catch (IOException e) {
736                         return null;
737                     }
738                 }
739             }
740         }
741     }
742
743     /** Test whether the document is modified.
744     * @return <code>true</code> if the document is in memory and is modified;
745     * otherwise <code>false</code>
746     */

747     public boolean isModified() {
748         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
749         if (redirect != null) {
750             return redirect.isModified();
751         }
752         return cesEnv().isModified();
753     }
754
755     /** Save the document in this thread.
756     * Create 'orig' document for the case that the save would fail.
757     * @exception IOException on I/O error
758     */

759     public void saveDocument() throws IOException {
760         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
761         if (redirect != null) {
762             redirect.saveDocument();
763             return;
764         }
765         // #17714: Don't try to save unmodified doc.
766
if (!cesEnv().isModified()) {
767             return;
768         }
769
770         //#32777: check that file was not modified externally.
771
// If it was then cancel saving operation. It is not absolutely
772
// correct, but there is no other way.
773
if (lastSaveTime != -1) {
774             externallyModified = false;
775
776             // asking for time should if necessary refresh the underlaying object
777
// (eg. FileObject) and this change can result in document reload task
778
// which will set externallyModified to true
779
cesEnv().getTime();
780
781             if (externallyModified) {
782                 // save operation must be cancelled now. The user get message box
783
// asking user to reload externally modified file.
784
return;
785             }
786         }
787
788         final StyledDocument myDoc = getDocument();
789         if (myDoc == null) {
790             return;
791         }
792
793         // save the document as a reader
794
class SaveAsReader implements Runnable JavaDoc {
795             private boolean doMarkAsUnmodified;
796             private IOException ex;
797
798             public void run() {
799                 try {
800                     OutputStream os = null;
801
802                     // write the document
803
long oldSaveTime = lastSaveTime;
804
805                     try {
806                         setLastSaveTime(-1);
807                         os = new BufferedOutputStream(cesEnv().outputStream());
808                         saveFromKitToStream(myDoc, kit, os);
809
810                         os.close(); // performs firing
811
os = null;
812
813                         // remember time of last save
814
ERR.fine("Save ok, assign new time, while old was: " + oldSaveTime); // NOI18N
815
setLastSaveTime(System.currentTimeMillis());
816                         
817                         doMarkAsUnmodified = true;
818                         ERR.fine("doMarkAsUnmodified"); // NOI18N
819
} catch (BadLocationException ex) {
820                         Exceptions.printStackTrace(ex);
821                     } finally {
822                         if (lastSaveTime == -1) { // restore for unsuccessful save
823
ERR.fine("restoring old save time"); // NOI18N
824
setLastSaveTime(oldSaveTime);
825                         }
826
827                         if (os != null) { // try to close if not yet done
828
os.close();
829                         }
830                     }
831
832                     // Insert before-save undo event to enable unmodifying undo
833
getUndoRedo().undoableEditHappened(new UndoableEditEvent JavaDoc(this, new BeforeSaveEdit(lastSaveTime)));
834
835                     // update cached info about lines
836
updateLineSet(true);
837                     // updateTitles(); radim #58266
838
} catch (IOException e) {
839                     this.ex = e;
840                 }
841             }
842
843             public void after() throws IOException {
844                 if (doMarkAsUnmodified) {
845                     callNotifyUnmodified();
846                 }
847
848                 if (ex != null) {
849                     throw ex;
850                 }
851             }
852         }
853
854         SaveAsReader saveAsReader = new SaveAsReader();
855         myDoc.render(saveAsReader);
856         saveAsReader.after();
857     }
858
859     /**
860      * Gets editor panes opened by this support.
861      * Can be called from AWT event thread only.
862      *
863      * @return a non-empty array of panes, or null
864      * @see EditorCookie#getOpenedPanes
865      */

866     public JEditorPane JavaDoc[] getOpenedPanes() {
867         // expected in AWT only
868
assert SwingUtilities.isEventDispatchThread(); // NOI18N
869
CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
870         if (redirect != null) {
871             return redirect.getOpenedPanes();
872         }
873
874         LinkedList<JEditorPane JavaDoc> ll = new LinkedList<JEditorPane JavaDoc>();
875         Enumeration en = allEditors.getComponents();
876
877         while (en.hasMoreElements()) {
878             CloneableTopComponent ctc = (CloneableTopComponent) en.nextElement();
879             Pane JavaDoc ed = (Pane JavaDoc) ctc.getClientProperty(PROP_PANE);
880
881             if ((ed == null) && ctc instanceof Pane JavaDoc) {
882                 ed = (Pane JavaDoc) ctc;
883             }
884
885             if (ed != null) {
886                 // #23491: pane could be still null, not yet shown component.
887
// [PENDING] Right solution? TopComponent opened, but pane not.
888
JEditorPane JavaDoc p = ed.getEditorPane();
889
890                 if (p == null) {
891                     continue;
892                 }
893
894                 if (getLastSelected() == ed) {
895                     ll.addFirst(p);
896                 } else {
897                     ll.add(p);
898                 }
899             } else {
900                 throw new IllegalStateException JavaDoc("No reference to Pane. Please file a bug against openide/text");
901             }
902         }
903
904         return ll.isEmpty() ? null : ll.toArray(new JEditorPane JavaDoc[ll.size()]);
905     }
906
907     /** Returns the lastly selected Pane or null
908      */

909     final Pane JavaDoc getLastSelected() {
910         Reference JavaDoc<Pane JavaDoc> r = lastSelected;
911
912         return (r == null) ? null : r.get();
913     }
914
915     final void setLastSelected(Pane JavaDoc lastSelected) {
916         this.lastSelected = new WeakReference JavaDoc<Pane JavaDoc>(lastSelected);
917     }
918
919     //
920
// LineSet interface impl
921
//
922

923     /** Get the line set for all paragraphs in the document.
924     * @return positions of all paragraphs on last save
925     */

926     public Line.Set getLineSet() {
927         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
928         if (redirect != null) {
929             return redirect.getLineSet();
930         }
931         return updateLineSet(false);
932     }
933
934     /** Lazyly creates or finds already created map for internal use.
935      */

936     final Map<Line,Reference JavaDoc<Line>> findWeakHashMap() {
937         // any lock not hold for too much time will do as we do not
938
// call outside in the sync block
939
synchronized (LOCK_PRINTING) {
940             if (lineSetWHM != null) {
941                 return lineSetWHM;
942             }
943
944             lineSetWHM = new WeakHashMap<Line,Reference JavaDoc<Line>>();
945
946             return lineSetWHM;
947         }
948     }
949
950     //
951
// Print interface
952
//
953

954     /** A printing implementation suitable for {@link org.openide.cookies.PrintCookie}. */
955     public void print() {
956         CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
957         if (redirect != null) {
958             redirect.print();
959             return;
960         }
961         // XXX should this run synch? can be slow for an enormous doc
962
synchronized (LOCK_PRINTING) {
963             if (printing) {
964                 return;
965             }
966
967             printing = true;
968         }
969
970         try {
971             PrinterJob JavaDoc job = PrinterJob.getPrinterJob();
972             Object JavaDoc o = NbDocument.findPageable(openDocument());
973
974             if (o instanceof Pageable JavaDoc) {
975                 job.setPageable((Pageable JavaDoc) o);
976             } else {
977                 PageFormat JavaDoc pf = PrintSettings.getPageFormat(job);
978                 job.setPrintable((Printable JavaDoc) o, pf);
979             }
980
981             if (job.printDialog()) {
982                 job.print();
983             }
984         } catch (FileNotFoundException e) {
985             notifyProblem(e, "CTL_Bad_File"); // NOI18N
986
} catch (IOException e) {
987             Exceptions.printStackTrace(e);
988         } catch (PrinterAbortException JavaDoc e) { // user exception
989
notifyProblem(e, "CTL_Printer_Abort"); // NOI18N
990
}catch (PrinterException JavaDoc e) {
991             notifyProblem(e, "EXC_Printer_Problem"); // NOI18N
992
} finally {
993             synchronized (LOCK_PRINTING) {
994                 printing = false;
995             }
996         }
997     }
998
999     private static void notifyProblem(Exception JavaDoc e, String JavaDoc key) {
1000        String JavaDoc msg = NbBundle.getMessage(CloneableEditorSupport.class, key, e.getLocalizedMessage());
1001        Exceptions.attachLocalizedMessage(e, msg);
1002        DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Exception(e));
1003    }
1004
1005    //
1006
// Methods overriden from CloneableOpenSupport
1007
//
1008

1009    /** Prepares document, creates and initializes
1010     * new <code>CloneableEditor</code> component.
1011     * Typically do not override this method.
1012     * For creating your own <code>CloneableEditor</code> type component
1013     * override {@link #createCloneableEditor} method.
1014     *
1015     * @return the {@link CloneableEditor} for this support
1016     */

1017    protected CloneableTopComponent createCloneableTopComponent() {
1018        // initializes the document if not initialized
1019
prepareDocument();
1020
1021        Pane JavaDoc pane = createPane();
1022        pane.getComponent().putClientProperty(PROP_PANE, pane);
1023
1024        return pane.getComponent();
1025    }
1026
1027    protected Pane JavaDoc createPane() {
1028        CloneableEditor ed = createCloneableEditor();
1029        initializeCloneableEditor(ed);
1030
1031        return ed;
1032    }
1033    
1034    /**
1035     * Wraps the editor component in a custom component, allowing for creating
1036     * more complicated user interfaces which contain the editor UI in
1037     * an arbitrary place.
1038     *
1039     * <p>The default implementation merely returns the passed
1040     * <code>editorComponent</code> parameter.</p>
1041     *
1042     * @param editorComponent the component containing the editor
1043     * (usually not the JEditorPane, but some its ancestor).
1044     *
1045     * @return a component containing <code>editorComponent</code> or
1046     * <code>editorComponent</code> itself.
1047     *
1048     * @since 6.3
1049     */

1050    protected Component JavaDoc wrapEditorComponent(Component JavaDoc editorComponent) {
1051        return editorComponent;
1052    }
1053
1054    /** Should test whether all data is saved, and if not, prompt the user
1055    * to save.
1056    *
1057    * @return <code>true</code> if everything can be closed
1058    */

1059    protected boolean canClose() {
1060        if (cesEnv().isModified()) {
1061
1062            class SafeAWTAccess implements Runnable JavaDoc {
1063                boolean running;
1064                boolean finished;
1065                int ret;
1066                
1067                public void run() {
1068                    synchronized (this) {
1069                        running = true;
1070                        notifyAll();
1071                    }
1072                    
1073                    try {
1074                        ret = canCloseImpl();
1075                    } finally {
1076                        synchronized (this) {
1077                            finished = true;
1078                            notifyAll();
1079                        }
1080                    }
1081                }
1082                
1083                
1084                
1085                public synchronized void waitForResult() throws InterruptedException JavaDoc {
1086                    if (!running) {
1087                        wait(10000);
1088                    }
1089                    if (!running) {
1090                        throw new InterruptedException JavaDoc("Waiting 10s for AWT and nothing! Exiting to prevent deadlock"); // NOI18N
1091
}
1092                    
1093                    while (!finished) {
1094                        wait();
1095                    }
1096                }
1097            }
1098            
1099            
1100            SafeAWTAccess safe = new SafeAWTAccess();
1101            if (SwingUtilities.isEventDispatchThread()) {
1102                safe.run();
1103            } else {
1104                SwingUtilities.invokeLater(safe);
1105                try {
1106                    safe.waitForResult();
1107                } catch (InterruptedException JavaDoc ex) {
1108                    ERR.log(Level.INFO, null, ex);
1109                    return false;
1110                }
1111            }
1112            
1113            if (safe.ret == 0) {
1114                return false;
1115            }
1116
1117            if (safe.ret == 1) {
1118                try {
1119                    saveDocument();
1120                } catch (IOException e) {
1121                    Exceptions.printStackTrace(e);
1122
1123                    return false;
1124                }
1125            }
1126        }
1127
1128        return true;
1129    }
1130    
1131    /** @return 0 => cannot close, -1 can close and do not save, 1 can close and save */
1132    private int canCloseImpl() {
1133        String JavaDoc msg = messageSave();
1134
1135        ResourceBundle bundle = NbBundle.getBundle(CloneableEditorSupport.class);
1136
1137        JButton JavaDoc saveOption = new JButton JavaDoc(bundle.getString("CTL_Save")); // NOI18N
1138
saveOption.getAccessibleContext().setAccessibleDescription(bundle.getString("ACSD_CTL_Save")); // NOI18N
1139
saveOption.getAccessibleContext().setAccessibleName(bundle.getString("ACSN_CTL_Save")); // NOI18N
1140

1141        JButton JavaDoc discardOption = new JButton JavaDoc(bundle.getString("CTL_Discard")); // NOI18N
1142
discardOption.getAccessibleContext().setAccessibleDescription(bundle.getString("ACSD_CTL_Discard")); // NOI18N
1143
discardOption.getAccessibleContext().setAccessibleName(bundle.getString("ACSN_CTL_Discard")); // NOI18N
1144
discardOption.setMnemonic(bundle.getString("CTL_Discard_Mnemonic").charAt(0)); // NOI18N
1145

1146        NotifyDescriptor nd = new NotifyDescriptor(
1147                msg, bundle.getString("LBL_SaveFile_Title"), NotifyDescriptor.YES_NO_CANCEL_OPTION,
1148                NotifyDescriptor.QUESTION_MESSAGE,
1149                new Object JavaDoc[] { saveOption, discardOption, NotifyDescriptor.CANCEL_OPTION }, saveOption
1150            );
1151
1152        Object JavaDoc ret = DialogDisplayer.getDefault().notify(nd);
1153
1154        if (NotifyDescriptor.CANCEL_OPTION.equals(ret) || NotifyDescriptor.CLOSED_OPTION.equals(ret)) {
1155            return 0;
1156        }
1157
1158        if (saveOption.equals(ret)) {
1159            return 1;
1160        } else {
1161            return -1;
1162        }
1163    }
1164
1165    //
1166
// public methods provided by this class
1167
//
1168

1169    /** Test whether the document is in memory, or whether loading is still in progress.
1170    * @return <code>true</code> if document is loaded
1171    */

1172    public boolean isDocumentLoaded() {
1173        CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
1174        if (redirect != null) {
1175            return redirect.isDocumentLoaded();
1176        }
1177        return documentStatus != DOCUMENT_NO;
1178    }
1179
1180    /**
1181    * Set the MIME type for the document.
1182    * @param s the new MIME type
1183    */

1184    public void setMIMEType(String JavaDoc s) {
1185        CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
1186        if (redirect != null) {
1187            redirect.setMIMEType(s);
1188            return;
1189        }
1190        mimeType = s;
1191    }
1192
1193    /** Adds a listener for status changes. An event is fired
1194    * when the document is moved or removed from memory.
1195    * @param l new listener
1196    * @deprecated Deprecated since 3.40. Use {@link #addPropertyChangeListener} instead.
1197    * See also {@link org.openide.cookies.EditorCookie.Observable}.
1198    */

1199    @Deprecated JavaDoc
1200    public synchronized void addChangeListener(ChangeListener JavaDoc l) {
1201        if (listeners == null) {
1202            listeners = new HashSet<ChangeListener JavaDoc>(8);
1203        }
1204
1205        listeners.add(l);
1206    }
1207
1208    /** Removes a listener for status changes.
1209     * @param l listener to remove
1210    * @deprecated Deprecated since 3.40. Use {@link #removePropertyChangeListener} instead.
1211    * See also {@link org.openide.cookies.EditorCookie.Observable}.
1212    */

1213    @Deprecated JavaDoc
1214    public synchronized void removeChangeListener(ChangeListener JavaDoc l) {
1215        if (listeners != null) {
1216            listeners.remove(l);
1217        }
1218    }
1219
1220    // Position management methods
1221

1222    /** Create a position reference for the given offset.
1223    * The position moves as the document is modified and
1224    * reacts to closing and opening of the document.
1225    *
1226    * @param offset the offset to create position at
1227    * @param bias the Position.Bias for new creating position.
1228    * @return position reference for that offset
1229    */

1230    public final PositionRef createPositionRef(int offset, Position.Bias bias) {
1231        return new PositionRef(getPositionManager(), offset, bias);
1232    }
1233
1234    //
1235
// Methods that can be overriden by subclasses
1236
//
1237

1238    /** Allows subclasses to create their own version
1239     * of <code>CloneableEditor</code> component.
1240     * @return the {@link CloneableEditor} for this support
1241     */

1242    protected CloneableEditor createCloneableEditor() {
1243        return new CloneableEditor(this);
1244    }
1245
1246    /** Initialize the editor. This method is called after the editor component
1247     * is deserialized and also when the component is created. It allows
1248     * the subclasses to annotate the component with icon, selected nodes, etc.
1249     *
1250     * @param editor the editor that has been created and should be annotated
1251     */

1252    protected void initializeCloneableEditor(CloneableEditor editor) {
1253    }
1254
1255    /** Create an undo/redo manager.
1256    * This manager is then attached to the document, and listens to
1257    * all changes made in it.
1258    * <P>
1259    * The default implementation uses improved <code>UndoRedo.Manager</code>.
1260    *
1261    * @return the undo/redo manager
1262    */

1263    protected UndoRedo.Manager createUndoRedoManager() {
1264        return new CESUndoRedoManager(this);
1265    }
1266
1267    /** Returns an InputStream which reads the current data from this editor, taking into
1268     * account the encoding of the file. The returned InputStream will be useful for
1269     * example when passing the file to an external compiler or other tool, which
1270     * expects an input stream and which deals with encoding internally.<br>
1271     *
1272     * See also {@link #saveFromKitToStream}.
1273     *
1274     * @return input stream for the file. If the file is open in the editor (and possibly modified),
1275     * then the returned <code>InputStream</code> will contain the same data as if the file
1276     * was written out to the {@link CloneableEditorSupport.Env} (usually disk). So it will contain
1277     * guarded block markers etc. If the document is not loaded,
1278     * then the <code>InputStream</code> will be taken from the {@link CloneableEditorSupport.Env}.
1279     *
1280     * @throws IOException if saving the document to a virtual stream or other IO operation fails
1281     * @since 4.7
1282     */

1283    public InputStream getInputStream() throws IOException {
1284        CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
1285        if (redirect != null) {
1286            return redirect.getInputStream();
1287        }
1288        // Implementation note
1289
// Piped stream will not work, as we are in the same thread
1290
// Doing this in a different thread would need to lock the document for
1291
// reading through doc.render() while this stream is open, which may be unacceptable
1292
// So we copy the document in memory
1293
StyledDocument doc = getDocument();
1294
1295        if (doc == null) {
1296            return cesEnv().inputStream();
1297        }
1298
1299        ByteArrayOutputStream baos = new ByteArrayOutputStream();
1300
1301        try {
1302            saveFromKitToStream(doc, kit, baos);
1303        } catch (BadLocationException e) {
1304            //assert false : e;
1305
// should not happen
1306
ERR.log(Level.INFO, null, e);
1307            throw (IllegalStateException JavaDoc) new IllegalStateException JavaDoc(e.getMessage()).initCause(e);
1308        }
1309
1310        return new ByteArrayInputStream(baos.toByteArray());
1311    }
1312
1313    /**
1314     * Actually write file data to an output stream from an editor kit's document.
1315     * Called during a file save by {@link #saveDocument}.
1316     * <p>The default implementation just calls {@link EditorKit#write(OutputStream, Document, int, int) EditorKit.write(...)}.
1317     * Subclasses could override this to provide support for persistent guard blocks, for example.
1318     * @param doc the document to write from
1319     * @param kit the associated editor kit
1320     * @param stream the open stream to write to
1321     * @throws IOException if there was a problem writing the file
1322     * @throws BadLocationException should not normally be thrown
1323     * @see #loadFromStreamToKit
1324     */

1325    protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream stream)
1326    throws IOException, BadLocationException {
1327        kit.write(stream, doc, 0, doc.getLength());
1328    }
1329
1330    /**
1331     * Actually read file data into an editor kit's document from an input stream.
1332     * Called during a file load by {@link #prepareDocument}.
1333     * <p>The default implementation just calls {@link EditorKit#read(InputStream, Document, int) EditorKit.read(...)}.
1334     * Subclasses could override this to provide support for persistent guard blocks, for example.
1335     * @param doc the document to read into
1336     * @param stream the open stream to read from
1337     * @param kit the associated editor kit
1338     * @throws IOException if there was a problem reading the file
1339     * @throws BadLocationException should not normally be thrown
1340     * @see #saveFromKitToStream
1341     */

1342    protected void loadFromStreamToKit(StyledDocument doc, InputStream stream, EditorKit kit)
1343    throws IOException, BadLocationException {
1344        kit.read(stream, doc, 0);
1345    }
1346
1347    /** Reload the document in response to external modification.
1348    * @return task that reloads the document. It can be also obtained
1349    * by calling <tt>prepareDocument()</tt>.
1350    */

1351    protected Task reloadDocument() {
1352        ERR.fine("reloadDocument in " + Thread.currentThread()); // NOI18N
1353

1354        if (doc != null) {
1355            // acquire write access
1356
NbDocument.runAtomic(doc,
1357                                 new Runnable JavaDoc() {
1358
1359                                     public void run() {
1360                                         // UndoManager must be detached from document here because it will be attached in loadDocument()
1361
doc.removeUndoableEditListener(getUndoRedo());
1362                                         // Remember caret positions in all opened panes
1363
final JEditorPane JavaDoc[] panes = getOpenedPanes();
1364                                         final int[] carets;
1365
1366                                         if (panes != null) {
1367                                             carets = new int[panes.length];
1368                                             for (int i = 0; i < panes.length; i++) {
1369                                                 carets[i] = panes[i].getCaretPosition();
1370                                             }
1371                                         } else {
1372                                             carets = new int[0];
1373                                         }
1374                                         documentStatus = DOCUMENT_RELOADING;
1375                                         prepareDocumentRuntimeException = null;
1376                                         int targetStatus = DOCUMENT_NO;
1377
1378                                         try {
1379                                             // #24676. Reloading: Put positions into memory
1380
// and fire document is closing (little trick
1381
// to detach annotations).
1382
getPositionManager().documentClosed();
1383                                             updateLineSet(true);
1384                                             fireDocumentChange(doc, true);
1385                                             ERR.fine("clearDocument");
1386                                             clearDocument();
1387                                             // uses the listener's run method to initialize whole document
1388
prepareTask = new Task(getListener());
1389                                             ERR.fine("new prepare task: " +
1390                                                      prepareTask);
1391                                             prepareTask.run();
1392                                             ERR.fine("prepareTask finished");
1393                                             documentStatus = DOCUMENT_READY;
1394                                             fireDocumentChange(doc, false);
1395                                             // Confirm that whole loading succeeded
1396
targetStatus = DOCUMENT_READY;
1397                                         }
1398                                         catch (RuntimeException JavaDoc t) {
1399                                             prepareDocumentRuntimeException = t;
1400                                             prepareTask = null;
1401                                             Exceptions.printStackTrace(t);
1402                                             throw t;
1403                                         }
1404                                         catch (Error JavaDoc t) {
1405                                             prepareDocumentRuntimeException = t;
1406                                             prepareTask = null;
1407                                             Exceptions.printStackTrace(t);
1408                                             throw t;
1409                                         }
1410                                         finally {
1411                                             synchronized (getLock()) {
1412                                                 documentStatus = targetStatus;
1413                                                 getLock().notifyAll();
1414                                             }
1415                                         }
1416                                         ERR.fine("post-reload task posting to AWT");
1417                                         Runnable JavaDoc run = new Runnable JavaDoc() {
1418
1419                                             public void run() {
1420                                                 if (doc == null) {
1421                                                     return;
1422                                                 }
1423                                                 if (panes != null) {
1424                                                     for (int i = 0; i <
1425                                                                     panes.length; i++) {
1426                                                         // #26407 Adjusts caret position,
1427
// (reloaded doc could be shorter).
1428
int textLength = panes[i].getDocument().getLength();
1429
1430                                                         if (carets[i] >
1431                                                             textLength) {
1432                                                             carets[i] = textLength;
1433                                                         }
1434                                                         panes[i].setCaretPosition(carets[i]);
1435                                                     }
1436                                                 }
1437                                                 // XXX do this from AWT???
1438
ERR.fine("task-discardAllEdits");
1439                                                 getUndoRedo().discardAllEdits();
1440                                                 // Insert before-save undo event to enable unmodifying undo
1441
ERR.fine("task-undoableEditHappened");
1442                                                 getUndoRedo().undoableEditHappened(new UndoableEditEvent JavaDoc(CloneableEditorSupport.this,
1443                                                                                                          new BeforeSaveEdit(lastSaveTime)));
1444                                                 ERR.fine("task-check already modified");
1445                                                 // #57104 - if modified previously now it should become unmodified
1446
if (isAlreadyModified()) {
1447                                                     ERR.fine("task-callNotifyUnmodified");
1448                                                     callNotifyUnmodified();
1449                                                 }
1450                                                 updateLineSet(true);
1451                                                 ERR.fine("task-addUndoableEditListener");
1452                                                 // Add undoable listener after atomic change has finished
1453
doc.addUndoableEditListener(getUndoRedo());
1454                                             }
1455                                         };
1456
1457                                         if (doc != null) {
1458                                             ERR.fine("Posting the AWT runnable: " +
1459                                                      run);
1460                                             SwingUtilities.invokeLater(run);
1461                                             ERR.fine("Posted in " +
1462                                                      Thread.currentThread());
1463                                         }
1464                                     }
1465                                 });
1466
1467            return prepareTask;
1468        }
1469
1470        return prepareDocument();
1471    }
1472
1473    /**
1474     * Gets an <code>EditorKit</code> from Netbeans registry. The method looks
1475     * in the <code>MimeLookup</code> for <code>EditorKit</code>s registered for
1476     * the mime-path passed in and returns the first one it finds. If there is
1477     * no <code>EditorKit</code> registered for the mime-path it will fall back
1478     * to the 'text/plain' <code>EditorKit</code> and eventually to its own
1479     * default kit.
1480     *
1481     * <div class="nonnormative">
1482     * <p>A mime-path is a concatenation of one or more mime-types allowing to
1483     * address fragments of text with a different mime-type than the mime-type
1484     * of a document that contains those fragments. As an example you can use
1485     * a JSP page containing a java scriplet. The JSP page is a document of
1486     * 'text/x-jsp' mime-type, while the mime-type of the java scriplet is 'text/x-java'.
1487     * When accessing settings or services such as an 'EditorKit' for java scriplets
1488     * embedded in a JSP page the scriplet's mime-path 'text/x-jsp/text/x-java'
1489     * should be used.
1490     * </p>
1491     * <p>If you are trying to get an 'EditorKit' for the whole document you can
1492     * simply pass in the document's mime-type (e.g. 'text/x-java'). For the main
1493     * document its mime-type and mime-path are the same.
1494     * </div>
1495     *
1496     * @param mimePath The mime-path to find an <code>EditorKit</code> for.
1497     *
1498     * @return The <code>EditorKit</code> implementation registered for the given mime-path.
1499     * @see org.netbeans.api.editor.mimelookup.MimeLookup
1500     * @since org.openide.text 6.12
1501     */

1502    public static EditorKit getEditorKit(String JavaDoc mimePath) {
1503        Lookup lookup = MimeLookup.getLookup(MimePath.parse(mimePath));
1504        EditorKit kit = lookup.lookup(EditorKit.class);
1505        
1506        if (kit == null) {
1507            // Try 'text/plain'
1508
lookup = MimeLookup.getLookup(MimePath.parse("text/plain"));
1509            kit = lookup.lookup(EditorKit.class);
1510        }
1511        
1512        // Don't use the prototype instance straightaway
1513
return kit != null ? (EditorKit) kit.clone() : new PlainEditorKit();
1514    }
1515    
1516    /** Creates editor kit for this source.
1517    * @return editor kit
1518    */

1519    protected EditorKit createEditorKit() {
1520        if (kit != null) {
1521            return kit;
1522        }
1523
1524        if (mimeType != null) {
1525            kit = getEditorKit(mimeType);
1526        } else {
1527            String JavaDoc defaultMIMEType = cesEnv().getMimeType();
1528            kit = getEditorKit(defaultMIMEType);
1529        }
1530
1531        return kit;
1532    }
1533
1534    /** Method that can be overriden by children to create empty
1535    * styled document or attach additional document properties to it.
1536    *
1537    * @param kit the kit to use
1538    * @return styled document to use
1539    */

1540    protected StyledDocument createStyledDocument(EditorKit kit) {
1541        StyledDocument sd = createNetBeansDocument(kit.createDefaultDocument());
1542        sd.putProperty("mimeType", (mimeType != null) ? mimeType : cesEnv().getMimeType()); // NOI18N
1543

1544        return sd;
1545    }
1546
1547    /** Notification method called when the document become unmodified.
1548    * Called after save or after reload of document.
1549    * <P>
1550    * This implementation simply marks the associated
1551    * environement unmodified and updates titles of all components.
1552    */

1553    protected void notifyUnmodified() {
1554        env.unmarkModified();
1555        updateTitles();
1556    }
1557
1558    /** Conditionally calls notifyModified
1559     * @return true if the modification was allowed, false if it should be prohibited
1560     */

1561    final boolean callNotifyModified() {
1562        // #57104 - when reverting undo the revertingUndoOrReloading flag is set
1563
// to prevent infinite undoing which could happen now due to fix #56963
1564
// (undoable edit being undone in the document notifies
1565
// document's modification listener to mark the file as modified).
1566
// Maybe clearing alreadyModified flag
1567
// AFTER revertPreviousOrUpcomingUndo() could suffice as well
1568
// instead of the revertingUndoOrReloading flag.
1569
// Also notifyModified() is not called during reloadDocument()
1570
// to prevent situation when output stream is taken from the file
1571
// (for which CloneableEditorSupport exists) under file's lock
1572
// and once closed (still under file's lock) the CES is trying to reload
1573
// the file calling notifyModified() that tries to grab the lock
1574
// and fails leading to undoing of the file's content to the one
1575
// before the reload.
1576
if (!isAlreadyModified() && !revertingUndoOrReloading) {
1577            setAlreadyModified(true);
1578
1579            if (!notifyModified()) {
1580                setAlreadyModified(false);
1581                revertingUndoOrReloading = true;
1582                revertPreviousOrUpcomingUndo();
1583                revertingUndoOrReloading = false;
1584
1585                return false;
1586            }
1587        }
1588
1589        return true;
1590    }
1591
1592    final void callNotifyUnmodified() {
1593        setAlreadyModified(false);
1594        notifyUnmodified();
1595    }
1596
1597    /** Called when the document is being modified.
1598    * The responsibility of this method is to inform the environment
1599    * that its document is modified. Current implementation
1600    * Just calls env.setModified (true) to notify it about
1601    * modification.
1602    *
1603    * @return true if the environment accepted being marked as modified
1604    * or false if it refused it and the document should still be unmodified
1605    */

1606    protected boolean notifyModified() {
1607        boolean locked = true;
1608
1609        try {
1610            env.markModified();
1611        } catch (final UserQuestionException ex) {
1612            synchronized (this) {
1613                if (!this.inUserQuestionExceptionHandler) {
1614                    this.inUserQuestionExceptionHandler = true;
1615                    RequestProcessor.getDefault().post(new Runnable JavaDoc() {
1616
1617                                                           public void run() {
1618                                                               NotifyDescriptor nd = new NotifyDescriptor.Confirmation(ex.getLocalizedMessage(),
1619                                                                                                                       NotifyDescriptor.YES_NO_OPTION);
1620                                                               Object JavaDoc res = DialogDisplayer.getDefault().notify(nd);
1621
1622                                                               if (NotifyDescriptor.OK_OPTION.equals(res)) {
1623                                                                   try {
1624                                                                       ex.confirmed();
1625                                                                   }
1626                                                                   catch (IOException ex1) {
1627                                                                       Exceptions.printStackTrace(ex1);
1628                                                                   }
1629                                                               }
1630                                                               synchronized (CloneableEditorSupport.this) {
1631                                                                   CloneableEditorSupport.this.inUserQuestionExceptionHandler = false;
1632                                                               }
1633                                                           }
1634                                                       });
1635                }
1636            }
1637
1638            locked = false;
1639        } catch (IOException e) { // locking failed
1640

1641            String JavaDoc message = null;
1642
1643            if (e.getMessage() != e.getLocalizedMessage()) {
1644                message = e.getLocalizedMessage();
1645            } else {
1646                message = Exceptions.findLocalizedMessage(e);
1647            }
1648
1649            if (message != null) {
1650                StatusDisplayer.getDefault().setStatusText(message);
1651            }
1652
1653            locked = false;
1654        }
1655
1656        if (!locked) {
1657            return false;
1658        }
1659
1660        // source modified, remove it from tab-reusing slot
1661
lastReusable.clear();
1662        updateTitles();
1663
1664        return true;
1665    }
1666
1667    /** Resets listening on <code>UndoRedo</code>,
1668     * and in case next undo edit comes, schedules processesing of it.
1669     * Used to revert modification e.g. of document of [read-only] env. */

1670    private void revertPreviousOrUpcomingUndo() {
1671        UndoRedo.Manager ur = getUndoRedo();
1672        Listener l = getListener();
1673
1674        if (Boolean.TRUE.equals(getDocument().getProperty("supportsModificationListener"))) { // NOI18N
1675

1676            // revert undos now
1677
SearchBeforeModificationEdit edit = new SearchBeforeModificationEdit();
1678
1679            try {
1680                for (;;) {
1681                    edit.delegate = null;
1682                    ur.undoableEditHappened(new UndoableEditEvent JavaDoc(getDocument(), edit));
1683
1684                    if (edit.delegate == null) break; // no previous edit
1685

1686                    if (edit.delegate instanceof BeforeModificationEdit) {
1687                        if (edit.delegate != null) {
1688                            // undo anyway
1689
ur.undo();
1690                        }
1691
1692                        // and exit
1693
break;
1694                    }
1695
1696                    if (edit.delegate instanceof BeforeSaveEdit) {
1697                        break;
1698                    }
1699
1700                    // otherwise remove the edit
1701
ur.undo();
1702                }
1703            } catch (CannotUndoException JavaDoc ex) {
1704                // ok, cannot undo, just ignore this
1705
}
1706        } else {
1707            // revert upcomming undo
1708
l.setUndoTask(new Runnable JavaDoc() {
1709                    public void run() {
1710                        undoAll();
1711                    }
1712                }
1713            );
1714            ur.addChangeListener(l);
1715        }
1716    }
1717
1718    /** Creates <code>Runnable</code> which tries to make one undo. Helper method.
1719     * @see #revertUpcomingUndo */

1720    final void undoAll() {
1721        StyledDocument sd = doc;
1722
1723        if (sd == null) {
1724            // #20883, doc can be null(!), doCloseDocument was faster.
1725
return;
1726        }
1727
1728        UndoRedo ur = getUndoRedo();
1729        addRemoveDocListener(sd, false);
1730
1731        try {
1732            if (ur.canUndo()) {
1733                Toolkit.getDefaultToolkit().beep();
1734                ur.undo();
1735            }
1736        } catch (CannotUndoException JavaDoc cne) {
1737            ERR.log(Level.INFO, null, cne);
1738        } finally {
1739            addRemoveDocListener(sd, true);
1740        }
1741    }
1742
1743    /** Method that is called when all components of the support are
1744    * closed. The default implementation closes the document.
1745    *
1746    */

1747    protected void notifyClosed() {
1748        closeDocument();
1749    }
1750
1751    // XXX #25762 [PENDING] Needed protected method to allow subclasses to alter it.
1752

1753    /** Indicates whether the <code>Env</code> is read only. */
1754    boolean isEnvReadOnly() {
1755        return false;
1756    }
1757
1758    /** Allows access to the document without any checking.
1759    */

1760    final StyledDocument getDocumentHack() {
1761        return doc;
1762    }
1763
1764    /** Getter for context associated with this
1765    * data object.
1766    */

1767    final org.openide.util.Lookup getLookup() {
1768        return lookup;
1769    }
1770
1771    // LineSet methods .....................................................................
1772

1773    /** Updates the line set.
1774    * @param clear clear any cached set?
1775    * @return the set
1776    */

1777    Line.Set updateLineSet(boolean clear) {
1778        synchronized (getLock()) {
1779            if ((lineSet != null) && !clear) {
1780                return lineSet;
1781            }
1782
1783            Line.Set oldSet = lineSet;
1784
1785            if ((doc == null) || (documentStatus == DOCUMENT_RELOADING)) {
1786                lineSet = new EditorSupportLineSet.Closed(CloneableEditorSupport.this);
1787            } else {
1788                lineSet = new EditorSupportLineSet(CloneableEditorSupport.this, doc);
1789            }
1790
1791            return lineSet;
1792        }
1793    }
1794
1795    /** Loads the document for this object.
1796    * @param kit kit to use
1797    * @param d original document to load data into
1798    */

1799    private void loadDocument(EditorKit kit, StyledDocument doc)
1800    throws IOException {
1801        Throwable JavaDoc aProblem = null;
1802
1803        try {
1804            InputStream is = new BufferedInputStream(cesEnv().inputStream());
1805
1806            try {
1807                // read the document
1808
loadFromStreamToKit(doc, is, kit);
1809            } finally {
1810                is.close();
1811            }
1812        } catch (UserQuestionException ex) {
1813            throw ex;
1814        } catch (IOException ex) {
1815            aProblem = ex;
1816            throw ex;
1817        } catch (Exception JavaDoc e) { // incl. BadLocationException
1818
aProblem = e;
1819        } finally {
1820            if (aProblem != null) {
1821                final Throwable JavaDoc tmp = aProblem;
1822                SwingUtilities.invokeLater(new Runnable JavaDoc() {
1823
1824                                               public void run() {
1825                                                   Exceptions.attachLocalizedMessage(tmp,
1826                                                                                     NbBundle.getMessage(CloneableEditorSupport.class,
1827                                                                                                         "EXC_LoadDocument",
1828                                                                                                         messageName()));
1829                                                   Exceptions.printStackTrace(tmp);
1830                                               }
1831                                           });
1832            }
1833        }
1834    }
1835
1836    /** Closes all opened editors (if the user agrees) and
1837    * flushes content of the document to the file.
1838    *
1839    * @param ask ask whether to save the document or not?
1840    * @return <code>false</code> if the operation is cancelled
1841    */

1842    protected boolean close(boolean ask) {
1843        CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
1844        if (redirect != null) {
1845            return redirect.close(ask);
1846        }
1847        
1848        if (!super.close(ask)) {
1849            // if not all editors has been closed
1850
return false;
1851        }
1852
1853        notifyClosed();
1854
1855        return true;
1856    }
1857
1858    /** Clears all data from memory.
1859    */

1860    private void closeDocument() {
1861        synchronized (getLock()) {
1862            while (true) {
1863                switch (documentStatus) {
1864                case DOCUMENT_NO:
1865                    return;
1866
1867                case DOCUMENT_LOADING:
1868                case DOCUMENT_RELOADING:
1869                // let it flow to default:
1870
// openDocumentImpl();
1871
// break; // try to close again
1872
default:
1873                    doCloseDocument();
1874
1875                    return;
1876                }
1877            }
1878        }
1879    }
1880
1881    /** Is called under getLock () to close the document.
1882     */

1883    private void doCloseDocument() {
1884        prepareTask = null;
1885
1886        // notifies the support that
1887
cesEnv().removePropertyChangeListener(getListener());
1888        callNotifyUnmodified();
1889
1890        if (doc != null) {
1891            doc.removeUndoableEditListener(getUndoRedo());
1892            addRemoveDocListener(doc, false);
1893        }
1894
1895        if (positionManager != null) {
1896            positionManager.documentClosed();
1897
1898            documentStatus = DOCUMENT_NO;
1899            fireDocumentChange(doc, true);
1900        }
1901
1902        documentStatus = DOCUMENT_NO;
1903        doc = null;
1904        kit = null;
1905
1906        getUndoRedo().discardAllEdits();
1907        updateLineSet(true);
1908    }
1909
1910    /** Handles the actual reload of document.
1911    * @param doReload false if we should first ask the user
1912    */

1913    private void checkReload(boolean doReload) {
1914        StyledDocument d;
1915
1916        synchronized (getLock()) {
1917            // don't try to reload if first load is in progress, cf.56413
1918
if (documentStatus != DOCUMENT_READY) {
1919                return;
1920            }
1921
1922            d = doc; // used with reload dialog - should not be null
1923
}
1924
1925        if (!doReload && !reloadDialogOpened) {
1926            String JavaDoc msg = NbBundle.getMessage(
1927                    CloneableEditorSupport.class, "FMT_External_change", // NOI18N
1928
d.getProperty(javax.swing.text.Document.TitleProperty)
1929                );
1930
1931            NotifyDescriptor nd = new NotifyDescriptor.Confirmation(msg, NotifyDescriptor.YES_NO_OPTION);
1932
1933            reloadDialogOpened = true;
1934
1935            try {
1936                Object JavaDoc ret = DialogDisplayer.getDefault().notify(nd);
1937
1938                if (NotifyDescriptor.YES_OPTION.equals(ret)) {
1939                    doReload = true;
1940                }
1941            } finally {
1942                reloadDialogOpened = false;
1943            }
1944        }
1945
1946        synchronized (getLock()) {
1947            // don't try to reload if first load is in progress, cf.56413
1948
if (documentStatus != DOCUMENT_READY) {
1949                return;
1950            }
1951
1952            if (doReload) {
1953                // #33165
1954
// reloadDocument() itself should be fast and the task
1955
// that it returns is scheduled to RP automatically
1956
reloadDocument();
1957
1958                /* #33165 - not posting to RP, reason is above
1959                                //Bugfix #9612: Call of reloadDocument() is now posted to
1960                                //RequestProcessor
1961                                RequestProcessor.getDefault().post(new Runnable() {
1962                                    public void run () {
1963                                        reloadDocument().waitFinished();
1964                                    }
1965                                });
1966                 */

1967            }
1968        }
1969    }
1970
1971    /** Creates netbeans document for a given document.
1972    * @param d document to use as underlaying one
1973    * @return styled document that could support Guarded.ATTRIBUTE
1974    */

1975    private static StyledDocument createNetBeansDocument(Document d) {
1976        if (d instanceof StyledDocument) {
1977            return (StyledDocument) d;
1978        } else {
1979            // create filter
1980
return new FilterDocument(d);
1981        }
1982    }
1983
1984    private final void fireDocumentChange(StyledDocument document, boolean closing) {
1985        fireStateChangeEvent(document, closing);
1986        firePropertyChange(EditorCookie.Observable.PROP_DOCUMENT,
1987                closing ? document : null,
1988                closing ? null : document);
1989    }
1990
1991    /** Fires a status change event to all listeners. */
1992    private final void fireStateChangeEvent(StyledDocument document, boolean closing) {
1993        if (listeners != null) {
1994            EnhancedChangeEvent event = new EnhancedChangeEvent(this, document, closing);
1995            ChangeListener JavaDoc[] ls;
1996
1997            synchronized (this) {
1998                ls = listeners.toArray(new ChangeListener JavaDoc[listeners.size()]);
1999            }
2000
2001            for (ChangeListener JavaDoc l : ls) {
2002                l.stateChanged(event);
2003            }
2004        }
2005    }
2006
2007    /** Updates titles of all editors.
2008    */

2009    protected void updateTitles() {
2010        Enumeration en = allEditors.getComponents();
2011
2012        while (en.hasMoreElements()) {
2013            CloneableTopComponent o = (CloneableTopComponent) en.nextElement();
2014            Pane JavaDoc e = (Pane JavaDoc) o.getClientProperty(PROP_PANE);
2015
2016            if ((e == null) && o instanceof Pane JavaDoc) {
2017                e = (Pane JavaDoc) o;
2018            }
2019
2020            if (e != null) {
2021                e.updateName();
2022            } else {
2023                throw new IllegalStateException JavaDoc("No reference to Pane. Please file a bug against openide/text");
2024            }
2025        }
2026    }
2027
2028    private static Reference JavaDoc<CloneableTopComponent> lastReusable = new WeakReference JavaDoc(null);
2029    
2030    // temporal - should be replaced by better impl in winsys
2031
private static void replaceTc(TopComponent orig, TopComponent open) {
2032        orig.close();
2033        open.open();
2034    }
2035
2036    // #18981. There could happen a thing also another class type
2037
// of CloneableTopCoponent then CloneableEditor could be in allEditors.
2038

2039    /** Opens a <code>CloneableEditor</code> component. */
2040    private Pane JavaDoc openPane(boolean reuse) {
2041        Pane JavaDoc ce = null;
2042        boolean displayMsgOpened = false;
2043
2044        synchronized (getLock()) {
2045            ce = getAnyEditor();
2046
2047            if (ce == null) {
2048                // no opened editor
2049
String JavaDoc msg = messageOpening();
2050
2051                if (msg != null) {
2052                    StatusDisplayer.getDefault().setStatusText(msg);
2053                }
2054
2055                // initializes the document if not initialized
2056
prepareDocument();
2057                ce = createPane();
2058                ce.getComponent().putClientProperty(PROP_PANE, ce);
2059                ce.getComponent().setReference(allEditors);
2060
2061                // signal opened msg should be displayed after subsequent open finishes
2062
displayMsgOpened = true;
2063            }
2064        }
2065
2066        // #36601 - open moved outside getLock() synchronization
2067
CloneableTopComponent ctc = ce.getComponent();
2068        if (reuse && displayMsgOpened) {
2069            CloneableTopComponent last = lastReusable.get();
2070            if (last != null) {
2071                replaceTc(last, ctc);
2072            } else {
2073                ctc.open();
2074            }
2075            lastReusable = new WeakReference JavaDoc(ctc);
2076        } else {
2077            ctc.open();
2078        }
2079        
2080        if (displayMsgOpened) {
2081            String JavaDoc msg = messageOpened();
2082
2083            if (msg == null) {
2084                msg = ""; // NOI18N
2085
}
2086
2087            StatusDisplayer.getDefault().setStatusText(msg);
2088        }
2089
2090        return ce;
2091    }
2092
2093    /** If one or more editors are opened finds one.
2094    * @return an editor or null if none is opened
2095    */

2096    private Pane JavaDoc getAnyEditor() {
2097        CloneableTopComponent ctc;
2098        ctc = allEditors.getArbitraryComponent();
2099
2100        if (ctc == null) {
2101            return null;
2102        }
2103
2104        Pane JavaDoc e = (Pane JavaDoc) ctc.getClientProperty(PROP_PANE);
2105
2106        if (e != null) {
2107            return e;
2108        } else {
2109            if (ctc instanceof Pane JavaDoc) {
2110                return (Pane JavaDoc) ctc;
2111            }
2112
2113            Enumeration en = allEditors.getComponents();
2114
2115            while (en.hasMoreElements()) {
2116                ctc = (CloneableTopComponent) en.nextElement();
2117                e = (Pane JavaDoc) ctc.getClientProperty(PROP_PANE);
2118
2119                if (e != null) {
2120                    return e;
2121                } else {
2122                    if (ctc instanceof Pane JavaDoc) {
2123                        return (Pane JavaDoc) ctc;
2124                    }
2125
2126                    throw new IllegalStateException JavaDoc("No reference to Pane. Please file a bug against openide/text");
2127                }
2128            }
2129
2130            return null;
2131        }
2132    }
2133   
2134    final Pane JavaDoc openReuse(final PositionRef pos, final int column, int mode) {
2135        if (mode == Line.SHOW_REUSE_NEW) lastReusable.clear();
2136        return openAtImpl(pos, column, true);
2137    }
2138    
2139    /** Forcibly create one editor component. Then set the caret
2140    * to the given position.
2141    * @param pos where to place the caret
2142    * @param column where to place the caret
2143    * @return always non-<code>null</code> editor
2144    * @since 5.2
2145    */

2146    protected final Pane JavaDoc openAt(final PositionRef pos, final int column) {
2147        return openAtImpl(pos, column, false);
2148    }
2149    /** Forcibly create one editor component. Then set the caret
2150    * to the given position.
2151    * @param pos where to place the caret
2152    * @param column where to place the caret
2153    * @param reuse if true, the infrastructure tries to reuse other, already opened editor
2154     * for the purpose of opening this file/line.
2155    * @return always non-<code>null</code> editor
2156    */

2157    private final Pane JavaDoc openAtImpl(final PositionRef pos, final int column, boolean reuse) {
2158        CloneableEditorSupport redirect = CloneableEditorSupportRedirector.findRedirect(this);
2159        if (redirect != null) {
2160            return redirect.openAtImpl(pos, column, reuse);
2161        }
2162        final Pane JavaDoc e = openPane(reuse);
2163        final Task t = prepareDocument();
2164        e.ensureVisible();
2165        class Selector implements TaskListener, Runnable JavaDoc {
2166            public void taskFinished(org.openide.util.Task t2) {
2167                javax.swing.SwingUtilities.invokeLater(this);
2168                t2.removeTaskListener(this);
2169            }
2170
2171            public void run() {
2172                // #25435. Pane can be null.
2173
JEditorPane JavaDoc ePane = e.getEditorPane();
2174
2175                if (ePane == null) {
2176                    return;
2177                }
2178
2179                StyledDocument doc = getDocument();
2180
2181                if (doc == null) {
2182                    return; // already closed or error loading
2183
}
2184
2185                Caret caret = ePane.getCaret();
2186
2187                if (caret == null) {
2188                    return;
2189                }
2190
2191                int offset;
2192
2193                if (column >= 0) {
2194                    javax.swing.text.Element JavaDoc el = NbDocument.findLineRootElement(doc);
2195                    el = el.getElement(el.getElementIndex(pos.getOffset()));
2196                    offset = el.getStartOffset() + column;
2197
2198                    if (offset > el.getEndOffset()) {
2199                        offset = el.getEndOffset();
2200                    }
2201                } else {
2202                    offset = pos.getOffset();
2203                }
2204
2205                caret.setDot(offset);
2206            }
2207        }
2208        t.addTaskListener(new Selector());
2209
2210        return e;
2211    }
2212
2213    /** Access to lock on operations on the support
2214    */

2215    final Object JavaDoc getLock() {
2216        return allEditors;
2217    }
2218
2219    /** Accessor to the <code>Listener</code> instance, lazy created on demand.
2220     * The instance serves as a listener on document, environment
2221     * and also provides document initialization task for this support.
2222     * @see Listener */

2223    private Listener getListener() {
2224        // Should not need to lock; it is always first
2225
// called within a synchronized(getLock()) block anyway.
2226
if (listener == null) {
2227            listener = new Listener();
2228        }
2229
2230        return listener;
2231    }
2232
2233    // [pnejedly]: helper for 40766 test
2234
void howToReproduceDeadlock40766(boolean beforeLock) {
2235    }
2236
2237    /** Make sure we log every access to last save time.
2238     * @param lst the time in millis of last save
2239     */

2240    final void setLastSaveTime(long lst) {
2241        ERR.fine("Setting new lastSaveTime to " + lst);
2242        this.lastSaveTime = lst;
2243    }
2244
2245    final boolean isAlreadyModified() {
2246        return alreadyModified;
2247    }
2248
2249    final void setAlreadyModified(boolean alreadyModified) {
2250        ERR.log(Level.FINE, null, new Exception JavaDoc("Setting to modified: " + alreadyModified));
2251
2252        this.alreadyModified = alreadyModified;
2253    }
2254
2255    //
2256
// Interfaces to abstract away from the DataSystem and FileSystem level
2257
//
2258

2259    /** Interface for providing data for the support and also
2260    * locking the source of data.
2261    */

2262    public static interface Env extends CloneableOpenSupport.Env {
2263        /** property that is fired when time of the data is changed */
2264        public static final String JavaDoc PROP_TIME = "time"; // NOI18N
2265

2266        /** Obtains the input stream.
2267         * @return an input stream permitting the document to be loaded
2268        * @exception IOException if an I/O error occures
2269        */

2270        public InputStream inputStream() throws IOException;
2271
2272        /** Obtains the output stream.
2273         * @return an output stream permitting the document to be saved
2274        * @exception IOException if an I/O error occures
2275        */

2276        public OutputStream outputStream() throws IOException;
2277
2278        /**
2279         * Gets the last modification time for the document.
2280         * @return the date and time when the document is considered to have been
2281         * last changed
2282         */

2283        public Date getTime();
2284
2285        /** Mime type of the document.
2286        * @return the mime type to use for the document
2287        */

2288        public String JavaDoc getMimeType();
2289    }
2290
2291    /** Describes one existing editor.
2292     */

2293    public interface Pane {
2294        /**
2295         * get the editor pane component represented by this wrapper.
2296         */

2297        public JEditorPane JavaDoc getEditorPane();
2298
2299        /**
2300         * Get the TopComponent that contains the EditorPane
2301         */

2302        public CloneableTopComponent getComponent();
2303
2304        public void updateName();
2305
2306        /**
2307         * callback for the Pane implementation to adjust itself to the openAt() request.
2308         */

2309        public void ensureVisible();
2310    }
2311
2312    /** Default editor kit.
2313    */

2314    private static final class PlainEditorKit extends DefaultEditorKit implements ViewFactory {
2315        static final long serialVersionUID = -5788777967029507963L;
2316
2317        PlainEditorKit() {
2318        }
2319
2320        /** @return cloned instance
2321        */

2322        public Object JavaDoc clone() {
2323            return new PlainEditorKit();
2324        }
2325
2326        /** @return this (I am the ViewFactory)
2327        */

2328        public ViewFactory getViewFactory() {
2329            return this;
2330        }
2331
2332        /** Plain view for the element
2333        */

2334        public View create(Element elem) {
2335            return new WrappedPlainView(elem);
2336        }
2337
2338        /** Set to a sane font (not proportional!). */
2339        public void install(JEditorPane JavaDoc pane) {
2340            super.install(pane);
2341            pane.setFont(new Font JavaDoc("Monospaced", Font.PLAIN, pane.getFont().getSize() + 1)); //NOI18N
2342
}
2343    }
2344
2345    /** The listener that this support uses to communicate with
2346     * document, environment and also temporarilly on undoredo.
2347     */

2348    private final class Listener extends Object JavaDoc implements ChangeListener JavaDoc, DocumentListener JavaDoc, PropertyChangeListener JavaDoc,
2349        Runnable JavaDoc, java.beans.VetoableChangeListener JavaDoc {
2350        /** revert modification if asked */
2351        private boolean revertModifiedFlag;
2352
2353        /** Stores exception from loadDocument, can be set in run method */
2354        private IOException loadExc;
2355
2356        /** Stores temporarilly undo task for reverting prohibited changes.
2357         * @see CloneableEditorSupport#createUndoTask */

2358        private Runnable JavaDoc undoTask;
2359
2360        Listener() {
2361        }
2362
2363        /** Returns exception from loadDocument, caller thread can check
2364         * it after load task finishes. Returns null if no exception happened.
2365         * It resets loadExc to null. */

2366        public IOException checkLoadException() {
2367            IOException ret = loadExc;
2368
2369            // loadExc = null;
2370
return ret;
2371        }
2372
2373        /** Sets undo task used to revert prohibited change. */
2374        public void setUndoTask(Runnable JavaDoc undoTask) {
2375            this.undoTask = undoTask;
2376        }
2377
2378        /** Schedules reverting(undoing) of prohibited change.
2379         * Implements <code>ChangeListener</code>.
2380         * @see #revertUpcomingUndo */

2381        public void stateChanged(ChangeEvent JavaDoc evt) {
2382            getUndoRedo().removeChangeListener(this);
2383            undoTask.run();
2384
2385            //SwingUtilities.invokeLater(undoTask);
2386
undoTask = null;
2387        }
2388
2389        /** Gives notification that an attribute or set of attributes changed.
2390        * @param ev event describing the action
2391        */

2392        public void changedUpdate(DocumentEvent JavaDoc ev) {
2393            //modified(); (bugfix #1492)
2394
}
2395
2396        public void vetoableChange(PropertyChangeEvent JavaDoc evt)
2397        throws java.beans.PropertyVetoException JavaDoc {
2398            if ("modified".equals(evt.getPropertyName())) { // NOI18N
2399

2400                if (Boolean.TRUE.equals(evt.getNewValue())) {
2401                    boolean wasModified = isAlreadyModified();
2402
2403                    if (!callNotifyModified()) {
2404                        throw new java.beans.PropertyVetoException JavaDoc("Not allowed", evt); // NOI18N
2405
}
2406
2407                    revertModifiedFlag = !wasModified;
2408                } else {
2409                    if (revertModifiedFlag) {
2410                        callNotifyUnmodified();
2411                    }
2412                }
2413            }
2414        }
2415
2416        /** Gives notification that there was an insert into the document.
2417        * @param ev event describing the action
2418        */

2419        public void insertUpdate(DocumentEvent JavaDoc ev) {
2420            callNotifyModified();
2421            revertModifiedFlag = false;
2422        }
2423
2424        /** Gives notification that a portion of the document has been removed.
2425        * @param ev event describing the action
2426        */

2427        public void removeUpdate(DocumentEvent JavaDoc ev) {
2428            callNotifyModified();
2429            revertModifiedFlag = false;
2430        }
2431
2432        /** Listener to changes in the Env.
2433        */

2434        public void propertyChange(PropertyChangeEvent JavaDoc ev) {
2435            if (Env.PROP_TIME.equals(ev.getPropertyName())) {
2436                // empty new value means to force reload all the time
2437
final Date time = (Date) ev.getNewValue();
2438                
2439                ERR.fine("PROP_TIME new value: " + time);
2440                ERR.fine(" lastSaveTime: " + lastSaveTime);
2441                
2442                boolean reload = (lastSaveTime != -1) && ((time == null) || (time.getTime() > lastSaveTime));
2443                ERR.fine(" reload: " + reload);
2444
2445                if (reload) {
2446                    //#32777 - set externallyModified to true because file was externally modified
2447
externallyModified = true;
2448
2449                    // - post in AWT event thread because of possible dialog popup
2450
// - acquire the write access before checking, so there is no
2451
// clash in-between and we're safe for potential reload.
2452
SwingUtilities.invokeLater(
2453                        new Runnable JavaDoc() {
2454                            boolean inWriteAccess;
2455
2456                            public void run() {
2457                                if (!inWriteAccess) {
2458                                    inWriteAccess = true;
2459
2460                                    StyledDocument sd = doc;
2461
2462                                    if (sd == null) {
2463                                        return;
2464                                    }
2465
2466                                    // #57104 - avoid notifyModified() which takes file lock
2467
revertingUndoOrReloading = true;
2468                                    NbDocument.runAtomic(sd, this);
2469                                    revertingUndoOrReloading = false; // #57104
2470

2471                                    return;
2472                                }
2473
2474                                checkReload((time == null) || !isModified());
2475                            }
2476                        }
2477                    );
2478                }
2479            }
2480
2481            if (Env.PROP_MODIFIED.equals(ev.getPropertyName())) {
2482                CloneableEditorSupport.this.firePropertyChange(
2483                    EditorCookie.Observable.PROP_MODIFIED, ev.getOldValue(), ev.getNewValue()
2484                );
2485            }
2486        }
2487
2488        /** Initialization of the document.
2489        */

2490        public void run() {
2491            // synchronized (getLock ()) {
2492

2493            /* Remove existing listener before running the loading task
2494            * This should prevent firing of insertUpdate() during load (or reload)
2495            * which can prevent dedloks that sometimes occured during file reload.
2496            */

2497            addRemoveDocListener(doc, false);
2498
2499            try {
2500                loadExc = null;
2501                LOCAL_LOAD_TASK.set(true);
2502                loadDocument(kit, doc);
2503            } catch (IOException e) {
2504                loadExc = e;
2505                throw new DelegateIOExc(e);
2506            } finally {
2507                LOCAL_LOAD_TASK.set(null);
2508            }
2509
2510            // opening the document, inform position manager
2511
getPositionManager().documentOpened(doc);
2512
2513            // create new description of lines
2514
updateLineSet(true);
2515
2516            setLastSaveTime(System.currentTimeMillis());
2517
2518            // Insert before-save undo event to enable unmodifying undo
2519
getUndoRedo().undoableEditHappened(new UndoableEditEvent JavaDoc(this, new BeforeSaveEdit(lastSaveTime)));
2520
2521            // Start listening on changes in document
2522
addRemoveDocListener(doc, true);
2523        }
2524
2525        // }
2526
}
2527
2528    /** Generic undoable edit that delegates to the given undoable edit. */
2529    private class FilterUndoableEdit implements UndoableEdit JavaDoc {
2530        protected UndoableEdit JavaDoc delegate;
2531
2532        FilterUndoableEdit() {
2533        }
2534
2535        public void undo() throws CannotUndoException JavaDoc {
2536            if (delegate != null) {
2537                delegate.undo();
2538            }
2539        }
2540
2541        public boolean canUndo() {
2542            if (delegate != null) {
2543                return delegate.canUndo();
2544            } else {
2545                return false;
2546            }
2547        }
2548
2549        public void redo() throws CannotRedoException JavaDoc {
2550            if (delegate != null) {
2551                delegate.redo();
2552            }
2553        }
2554
2555        public boolean canRedo() {
2556            if (delegate != null) {
2557                return delegate.canRedo();
2558            } else {
2559                return false;
2560            }
2561        }
2562
2563        public void die() {
2564            if (delegate != null) {
2565                delegate.die();
2566            }
2567        }
2568
2569        public boolean addEdit(UndoableEdit JavaDoc anEdit) {
2570            if (delegate != null) {
2571                return delegate.addEdit(anEdit);
2572            } else {
2573                return false;
2574            }
2575        }
2576
2577        public boolean replaceEdit(UndoableEdit JavaDoc anEdit) {
2578            if (delegate != null) {
2579                return delegate.replaceEdit(anEdit);
2580            } else {
2581                return false;
2582            }
2583        }
2584
2585        public boolean isSignificant() {
2586            if (delegate != null) {
2587                return delegate.isSignificant();
2588            } else {
2589                return true;
2590            }
2591        }
2592
2593        public String JavaDoc getPresentationName() {
2594            if (delegate != null) {
2595                return delegate.getPresentationName();
2596            } else {
2597                return ""; // NOI18N
2598
}
2599        }
2600
2601        public String JavaDoc getUndoPresentationName() {
2602            if (delegate != null) {
2603                return delegate.getUndoPresentationName();
2604            } else {
2605                return ""; // NOI18N
2606
}
2607        }
2608
2609        public String JavaDoc getRedoPresentationName() {
2610            if (delegate != null) {
2611                return delegate.getRedoPresentationName();
2612            } else {
2613                return ""; // NOI18N
2614
}
2615        }
2616    }
2617
2618    /** Undoable edit that is put before the savepoint. Its replaceEdit()
2619     * method will consume and wrap the edit that precedes the save.
2620     * If the edit is added to the begining of the queue then
2621     * the isSignificant() implementation guarantees that the edit
2622     * will not be removed from the queue.
2623     * When redone it marks the document as not modified.
2624     */

2625    private class BeforeSaveEdit extends FilterUndoableEdit {
2626        private long saveTime;
2627
2628        BeforeSaveEdit(long saveTime) {
2629            this.saveTime = saveTime;
2630        }
2631
2632        public boolean replaceEdit(UndoableEdit JavaDoc anEdit) {
2633            if (delegate == null) {
2634                delegate = anEdit;
2635
2636                return true; // signal consumed
2637
}
2638
2639            return false;
2640        }
2641
2642        public boolean addEdit(UndoableEdit JavaDoc anEdit) {
2643            if (!(anEdit instanceof BeforeModificationEdit) && !(anEdit instanceof SearchBeforeModificationEdit)) {
2644                /* UndoRedo.addEdit() must not be done lazily
2645                 * because the edit must be "inserted" before the current one.
2646                 */

2647                getUndoRedo().addEdit(new BeforeModificationEdit(saveTime, anEdit));
2648
2649                return true;
2650            }
2651
2652            return false;
2653        }
2654
2655        public void redo() {
2656            super.redo();
2657
2658            if (saveTime == lastSaveTime) {
2659                justRevertedToNotModified = true;
2660            }
2661        }
2662
2663        public boolean isSignificant() {
2664            return (delegate != null);
2665        }
2666    }
2667
2668    /** Edit that is created by wrapping the given edit.
2669     * When undone it marks the document as not modified.
2670     */

2671    private class BeforeModificationEdit extends FilterUndoableEdit {
2672        private long saveTime;
2673
2674        BeforeModificationEdit(long saveTime, UndoableEdit JavaDoc delegate) {
2675            this.saveTime = saveTime;
2676            this.delegate = delegate;
2677            ERR.log(Level.FINE, null, new Exception JavaDoc("new BeforeModificationEdit(" + saveTime +")")); // NOI18N
2678
}
2679
2680        public boolean addEdit(UndoableEdit JavaDoc anEdit) {
2681            if ((delegate == null) && !(anEdit instanceof SearchBeforeModificationEdit)) {
2682                delegate = anEdit;
2683
2684                return true;
2685            }
2686
2687            return delegate.addEdit(anEdit);
2688        }
2689
2690        public void undo() {
2691            super.undo();
2692
2693            boolean res = saveTime == lastSaveTime;
2694            ERR.fine("Comparing saveTime and lastSaveTime: " + saveTime + "==" + lastSaveTime + " is " + res); // NOI18N
2695
if (res) {
2696                justRevertedToNotModified = true;
2697            }
2698        }
2699    }
2700
2701    /** This edit is used to search for BeforeModificationEdit in UndoRedo
2702     * manager. This is not much nice solution, but well, there is not
2703     * much other chances to get inside UndoRedo.
2704     */

2705    private class SearchBeforeModificationEdit extends FilterUndoableEdit {
2706        SearchBeforeModificationEdit() {
2707        }
2708
2709        public boolean replaceEdit(UndoableEdit JavaDoc anEdit) {
2710            if (delegate == null) {
2711                delegate = anEdit;
2712
2713                return true; // signal consumed
2714
}
2715
2716            return false;
2717        }
2718    }
2719
2720    /** An improved version of UndoRedo manager that locks document before
2721     * doing any other operations.
2722     */

2723    private final static class CESUndoRedoManager extends UndoRedo.Manager {
2724        private CloneableEditorSupport support;
2725
2726        public CESUndoRedoManager(CloneableEditorSupport c) {
2727            this.support = c;
2728            super.setLimit(1000);
2729        }
2730
2731        public void redo() throws javax.swing.undo.CannotRedoException JavaDoc {
2732            final StyledDocument myDoc = support.getDocument();
2733
2734            if (myDoc == null) {
2735                throw new javax.swing.undo.CannotRedoException JavaDoc(); // NOI18N
2736
}
2737
2738            support.justRevertedToNotModified = false;
2739            new RenderUndo(0, myDoc);
2740
2741            if (support.justRevertedToNotModified && support.isAlreadyModified()) {
2742                support.callNotifyUnmodified();
2743            }
2744        }
2745
2746        public void undo() throws javax.swing.undo.CannotUndoException JavaDoc {
2747            final StyledDocument myDoc = support.getDocument();
2748
2749            if (myDoc == null) {
2750                throw new javax.swing.undo.CannotUndoException JavaDoc(); // NOI18N
2751
}
2752
2753            support.justRevertedToNotModified = false;
2754            new RenderUndo(1, myDoc);
2755
2756            if (support.justRevertedToNotModified && support.isAlreadyModified()) {
2757                support.callNotifyUnmodified();
2758            }
2759        }
2760
2761        public boolean canRedo() {
2762            final StyledDocument myDoc = support.getDocument();
2763
2764            return new RenderUndo(2, myDoc).booleanResult;
2765        }
2766
2767        public boolean canUndo() {
2768            final StyledDocument myDoc = support.getDocument();
2769
2770            return new RenderUndo(3, myDoc).booleanResult;
2771        }
2772
2773        public int getLimit() {
2774            final StyledDocument myDoc = support.getDocument();
2775
2776            return new RenderUndo(4, myDoc).intResult;
2777        }
2778
2779        public void discardAllEdits() {
2780            final StyledDocument myDoc = support.getDocument();
2781            new RenderUndo(5, myDoc);
2782        }
2783
2784        public void setLimit(int l) {
2785            final StyledDocument myDoc = support.getDocument();
2786            new RenderUndo(6, myDoc, l);
2787        }
2788
2789        public boolean canUndoOrRedo() {
2790            final StyledDocument myDoc = support.getDocument();
2791
2792            return new RenderUndo(7, myDoc).booleanResult;
2793        }
2794
2795        public java.lang.String JavaDoc getUndoOrRedoPresentationName() {
2796            final StyledDocument myDoc = support.getDocument();
2797
2798            return new RenderUndo(8, myDoc).stringResult;
2799        }
2800
2801        public java.lang.String JavaDoc getRedoPresentationName() {
2802            final StyledDocument myDoc = support.getDocument();
2803
2804            return new RenderUndo(9, myDoc).stringResult;
2805        }
2806
2807        public java.lang.String JavaDoc getUndoPresentationName() {
2808            final StyledDocument myDoc = support.getDocument();
2809
2810            return new RenderUndo(10, myDoc).stringResult;
2811        }
2812
2813        public void undoOrRedo() throws javax.swing.undo.CannotUndoException JavaDoc, javax.swing.undo.CannotRedoException JavaDoc {
2814            final StyledDocument myDoc = support.getDocument();
2815
2816            if (myDoc == null) {
2817                throw new javax.swing.undo.CannotUndoException JavaDoc(); // NOI18N
2818
}
2819
2820            support.justRevertedToNotModified = false;
2821            new RenderUndo(11, myDoc);
2822
2823            if (support.justRevertedToNotModified && support.isAlreadyModified()) {
2824                support.callNotifyUnmodified();
2825            }
2826        }
2827
2828        private final class RenderUndo implements Runnable JavaDoc {
2829            private final int type;
2830            public boolean booleanResult;
2831            public int intResult;
2832            public String JavaDoc stringResult;
2833
2834            public RenderUndo(int type, StyledDocument doc) {
2835                this(type, doc, 0);
2836            }
2837
2838            public RenderUndo(int type, StyledDocument doc, int intValue) {
2839                this.type = type;
2840                this.intResult = intValue;
2841
2842                if (doc instanceof NbDocument.WriteLockable) {
2843                    ((NbDocument.WriteLockable) doc).runAtomic(this);
2844                } else {
2845                    // if the document is not one of "NetBeans ready"
2846
// that supports locking we do not have many
2847
// chances to do something. Maybe check for AbstractDocument
2848
// and call writeLock using reflection, but better than
2849
// that, let's leave this simple for now and wait for
2850
// bug reports (if any appear)
2851
run();
2852                }
2853            }
2854
2855            public void run() {
2856                switch (type) {
2857                case 0:
2858                    CESUndoRedoManager.super.redo();
2859
2860                    break;
2861
2862                case 1:
2863                    CESUndoRedoManager.super.undo();
2864
2865                    break;
2866
2867                case 2:
2868                    booleanResult = CESUndoRedoManager.super.canRedo();
2869
2870                    break;
2871
2872                case 3:
2873                    booleanResult = CESUndoRedoManager.super.canUndo();
2874
2875                    break;
2876
2877                case 4:
2878                    intResult = CESUndoRedoManager.super.getLimit();
2879
2880                    break;
2881
2882                case 5:
2883                    CESUndoRedoManager.super.discardAllEdits();
2884
2885                    break;
2886
2887                case 6:
2888                    CESUndoRedoManager.super.setLimit(intResult);
2889
2890                    break;
2891
2892                case 7:
2893                    CESUndoRedoManager.super.canUndoOrRedo();
2894
2895                    break;
2896
2897                case 8:
2898                    stringResult = CESUndoRedoManager.super.getUndoOrRedoPresentationName();
2899
2900                    break;
2901
2902                case 9:
2903                    stringResult = CESUndoRedoManager.super.getRedoPresentationName();
2904
2905                    break;
2906
2907                case 10:
2908                    stringResult = CESUndoRedoManager.super.getUndoPresentationName();
2909
2910                    break;
2911
2912                case 11:
2913                    CESUndoRedoManager.super.undoOrRedo();
2914
2915                    break;
2916
2917                default:
2918                    throw new IllegalArgumentException JavaDoc("Unknown type: " + type);
2919                }
2920            }
2921        }
2922    }
2923
2924    /** Special runtime exception that holds the original I/O failure.
2925     */

2926    private static final class DelegateIOExc extends IllegalStateException JavaDoc {
2927        public DelegateIOExc(IOException ex) {
2928            super(ex.getMessage());
2929            initCause(ex);
2930        }
2931    }
2932
2933}
2934
Popular Tags