KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > properties > PropertiesEditorSupport


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
20 package org.netbeans.modules.properties;
21
22 import java.awt.EventQueue JavaDoc;
23 import java.awt.Image JavaDoc;
24 import java.beans.*;
25 import java.io.*;
26 import java.lang.ref.Reference JavaDoc;
27 import java.lang.ref.WeakReference JavaDoc;
28 import java.util.Collections JavaDoc;
29 import java.util.Date JavaDoc;
30 import java.util.Enumeration JavaDoc;
31 import java.util.Set JavaDoc;
32 import java.util.WeakHashMap JavaDoc;
33 import javax.swing.JEditorPane JavaDoc;
34 import javax.swing.JLabel JavaDoc;
35 import javax.swing.text.BadLocationException JavaDoc;
36 import javax.swing.text.Document JavaDoc;
37 import javax.swing.text.EditorKit JavaDoc;
38 import javax.swing.text.StyledDocument JavaDoc;
39 import javax.swing.undo.CannotRedoException JavaDoc;
40 import javax.swing.undo.CannotUndoException JavaDoc;
41 import javax.swing.undo.UndoableEdit JavaDoc;
42 import org.openide.DialogDisplayer;
43 import org.openide.ErrorManager;
44 import org.openide.NotifyDescriptor;
45 import org.openide.awt.UndoRedo;
46 import org.openide.cookies.CloseCookie;
47 import org.openide.cookies.EditCookie;
48 import org.openide.cookies.EditorCookie;
49 import org.openide.cookies.PrintCookie;
50 import org.openide.cookies.SaveCookie;
51 import org.openide.filesystems.FileChangeAdapter;
52 import org.openide.filesystems.FileEvent;
53 import org.openide.filesystems.FileLock;
54 import org.openide.filesystems.FileObject;
55 import org.openide.filesystems.FileStatusEvent;
56 import org.openide.filesystems.FileStatusListener;
57 import org.openide.filesystems.FileSystem;
58 import org.openide.filesystems.FileStateInvalidException;
59 import org.openide.filesystems.FileUtil;
60 import org.openide.loaders.DataObject;
61 import org.openide.nodes.Node;
62 import org.openide.text.CloneableEditor;
63 import org.openide.text.CloneableEditorSupport;
64 import org.openide.util.HelpCtx;
65 import org.openide.util.Mutex;
66 import org.openide.util.NbBundle;
67 import org.openide.util.WeakListeners;
68 import org.openide.util.Utilities;
69 import org.openide.windows.CloneableOpenSupport;
70 import org.openide.util.Task;
71 import org.openide.util.TaskListener;
72 import org.openide.windows.TopComponent;
73
74 /**
75  * Support for viewing .properties files (EditCookie) by opening them in a text editor.
76  *
77  * @author Petr Jiricka, Peter Zavadsky
78  * @see org.openide.text.CloneableEditorSupport
79  */

80 public class PropertiesEditorSupport extends CloneableEditorSupport
81 implements EditCookie, EditorCookie.Observable, PrintCookie, CloseCookie, Serializable {
82     
83     /** */
84     static final String JavaDoc PROP_NON_ASCII_CHAR_READ = "org.netbeans.modules.properties.nonAsciiPresent"; //NOI18N
85
/** New lines in this file was delimited by '\n'. */
86     private static final byte NEW_LINE_N = 0;
87     /** New lines in this file was delimited by '\r'. */
88     private static final byte NEW_LINE_R = 1;
89     /** New lines in this file was delimited by '\r\n'. */
90     private static final byte NEW_LINE_RN = 2;
91     
92     /** */
93     private FileStatusListener fsStatusListener;
94     
95     /** The type of new lines. Default is <code>NEW_LINE_N</code>. */
96     private byte newLineType = NEW_LINE_N;
97     
98     /** Visible view of underlying file entry */
99     transient PropertiesFileEntry myEntry;
100     
101     /** Generated serial version UID. */
102     static final long serialVersionUID =1787354011149868490L;
103     
104     
105     /** Constructor. */
106     public PropertiesEditorSupport(PropertiesFileEntry entry) {
107         super(new Environment(entry),
108               org.openide.util.lookup.Lookups.singleton(entry.getDataObject()));
109         this.myEntry = entry;
110     }
111     
112     /**
113      * Overrides superclass method.
114      * Should test whether all data is saved, and if not, prompt the user
115      * to save. Called by my topcomponent when it wants to close its last topcomponent, but the table editor may still be open
116      * @return <code>true</code> if everything can be closed
117      */

118     protected boolean canClose () {
119         // if the table is open, can close without worries, don't remove the save cookie
120
if (hasOpenedTableComponent()){
121             return true;
122         }else{
123             DataObject propDO = myEntry.getDataObject();
124             if (propDO == null || !propDO.isModified()) return true;
125             return super.canClose();
126         }
127     }
128     
129     /**
130      * Overrides superclass method.
131      * @return the {@link CloneableEditor} for this support
132      */

133     protected CloneableEditor createCloneableEditor() {
134         return new PropertiesEditor(this);
135     }
136     
137     /**
138      *
139      */

140     final class FsStatusListener implements FileStatusListener, Runnable JavaDoc {
141         
142     /**
143      */

144     public void annotationChanged(FileStatusEvent ev) {
145             if (ev.isNameChange() && ev.hasChanged(myEntry.getFile())) {
146                 Mutex.EVENT.writeAccess(this);
147             }
148     }
149         
150     /**
151      */

152     public void run() {
153         updateEditorDisplayNames();
154     }
155     }
156     
157     /**
158      */

159     private void attachStatusListener() {
160         if (fsStatusListener != null) {
161             return; //already attached
162
}
163         
164         FileSystem fs;
165         try {
166             fs = myEntry.getFile().getFileSystem();
167         } catch (FileStateInvalidException ex) {
168             ErrorManager.getDefault().notify(ErrorManager.ERROR, ex);
169             return;
170         }
171         
172         fsStatusListener = new FsStatusListener();
173         fs.addFileStatusListener(
174                 FileUtil.weakFileStatusListener(fsStatusListener, fs));
175     }
176     
177     /**
178      */

179     private void updateEditorDisplayNames() {
180         assert EventQueue.isDispatchThread();
181         
182         final String JavaDoc title = messageName();
183         final String JavaDoc htmlTitle = messageHtmlName();
184         Enumeration JavaDoc en = allEditors.getComponents();
185         while (en.hasMoreElements()) {
186             TopComponent tc = (TopComponent) en.nextElement();
187             tc.setDisplayName(title);
188             tc.setHtmlDisplayName(htmlTitle);
189         }
190     }
191     
192     /**
193      */

194     protected void initializeCloneableEditor(CloneableEditor editor) {
195     ((PropertiesEditor) editor).initialize(myEntry);
196     }
197
198     /**
199      * Overrides superclass method.
200      * Let's the super method create the document and also annotates it
201      * with Title and StreamDescription properities.
202      * @param kit kit to user to create the document
203      * @return the document annotated by the properties
204      */

205     protected StyledDocument JavaDoc createStyledDocument(EditorKit JavaDoc kit) {
206         StyledDocument JavaDoc document = super.createStyledDocument(kit);
207         
208         // Set additional proerties to document.
209
// Set document name property. Used in CloneableEditorSupport.
210
document.putProperty(Document.TitleProperty, myEntry.getFile().toString());
211         
212         // Set dataobject to stream desc property.
213
document.putProperty(Document.StreamDescriptionProperty, myEntry.getDataObject());
214         
215         // hook the document to listen for any changes to update changes by
216
// reparsing the document
217
document.addDocumentListener(new javax.swing.event.DocumentListener JavaDoc() {
218             public void insertUpdate(javax.swing.event.DocumentEvent JavaDoc e) { changed();}
219             public void changedUpdate(javax.swing.event.DocumentEvent JavaDoc e) { changed();}
220             public void removeUpdate(javax.swing.event.DocumentEvent JavaDoc e) { changed();}
221             private void changed() {
222                 myEntry.getHandler().autoParse();
223             }
224         });
225         
226         return document;
227     }
228
229     /**
230      * Reads the file from the stream, filter the guarded section
231      * comments, and mark the sections in the editor. Overrides superclass method.
232      * @param document the document to read into
233      * @param inputStream the open stream to read from
234      * @param editorKit the associated editor kit
235      * @throws <code>IOException</code> if there was a problem reading the file
236      * @throws <code>BadLocationException</code> should not normally be thrown
237      * @see #saveFromKitToStream
238      */

239     protected void loadFromStreamToKit(StyledDocument JavaDoc document, InputStream inputStream, EditorKit JavaDoc editorKit)
240     throws IOException, BadLocationException JavaDoc {
241         NewLineReader newLineReader = new NewLineReader(inputStream);
242         
243         try {
244             editorKit.read(newLineReader, document, 0);
245             newLineType = newLineReader.getNewLineType();
246             if (newLineReader.wasNonAsciiCharacterRead()) {
247                 document.putProperty(PROP_NON_ASCII_CHAR_READ, Boolean.TRUE);
248             }
249         } finally {
250             newLineReader.close();
251         }
252     }
253
254     /**
255      * Adds new lines according actual value of <code>newLineType</code> variable.
256      * Overrides superclass method.
257      * @param document the document to write from
258      * @param editorKit the associated editor kit
259      * @param ouputStream the open stream to write to
260      * @throws IOException if there was a problem writing the file
261      * @throws BadLocationException should not normally be thrown
262      * @see #loadFromStreamToKit
263      */

264     protected void saveFromKitToStream(StyledDocument JavaDoc document, EditorKit JavaDoc editorKit, OutputStream outputStream)
265     throws IOException, BadLocationException JavaDoc {
266         Writer writer = new NewLineWriter(outputStream, newLineType);
267         
268         try {
269             editorKit.write(writer, document, 0, document.getLength());
270         } finally {
271             writer.flush();
272             writer.close();
273         }
274     }
275     
276     /**
277      * Adds a save cookie if the document has been marked modified. Overrides superclass method.
278      * @return <code>true</code> if the environment accepted being marked as modified
279      * or <code>false</code> if it refused it and the document should still be unmodified
280      */

281     protected boolean notifyModified () {
282         // Reparse file.
283
myEntry.getHandler().autoParse();
284
285         if (super.notifyModified()) {
286             ((Environment)env).addSaveCookie();
287             
288             return true;
289         } else {
290             return false;
291         }
292     }
293     
294     protected Task reloadDocument(){
295         Task tsk = super.reloadDocument();
296         tsk.addTaskListener(new TaskListener(){
297             public void taskFinished(Task task){
298                 myEntry.getHandler().autoParse();
299             }
300         });
301         return tsk;
302     }
303
304     /** Overrides superclass method. Adds checking for opened Table component. */
305     protected void notifyUnmodified () {
306         super.notifyUnmodified();
307         
308         ((Environment)env).removeSaveCookie();
309     }
310     
311     /**
312      */

313     public void open() {
314     super.open();
315     attachStatusListener();
316     }
317     
318     /** Overrides superclass method. Adds checking for opened Table panel. */
319     protected void notifyClosed() {
320         // Close document only in case there is not open table editor.
321
if(!hasOpenedTableComponent()) {
322             boolean wasModified = isModified();
323             super.notifyClosed();
324             if (wasModified) {
325                 // #21850. Don't reparse invalid or virtual file.
326
if(myEntry.getFile().isValid() && !myEntry.getFile().isVirtual()) {
327                     myEntry.getHandler().reparseNowBlocking();
328                 }
329             }
330         }
331     }
332
333     /**
334      * Overrides superclass abstract method.
335      * Message to display when an object is being opened.
336      * @return the message or null if nothing should be displayed
337      */

338     protected String JavaDoc messageOpening() {
339         String JavaDoc name = myEntry.getDataObject().getPrimaryFile().getName()+"("+Util.getLocaleLabel(myEntry)+")"; // NOI18N
340

341         return NbBundle.getMessage(
342             PropertiesEditorSupport.class,
343             "LBL_ObjectOpen", // NOI18N
344
name
345         );
346     }
347     
348     /**
349      * Overrides superclass abstract method.
350      * Message to display when an object has been opened.
351      * @return the message or null if nothing should be displayed
352      */

353     protected String JavaDoc messageOpened() {
354         String JavaDoc name = myEntry.getDataObject().getPrimaryFile().getName()+"("+Util.getLocaleLabel(myEntry)+")"; // NOI18N
355

356         return NbBundle.getMessage(
357             PropertiesEditorSupport.class,
358             "LBL_ObjectOpened", // NOI18N
359
name
360        );
361     }
362     
363     /**
364      */

365     private String JavaDoc getRawMessageName() {
366         return myEntry.getDataObject().getName()
367                + '(' + Util.getLocaleLabel(myEntry) + ')';
368     }
369     
370     /**
371      */

372     private String JavaDoc addModifiedInfo(String JavaDoc name) {
373         int version = isModified() ? (myEntry.getFile().canWrite() ? 1 : 2)
374                                    : (myEntry.getFile().canWrite() ? 3 : 0);
375         return NbBundle.getMessage(PropertiesEditorSupport.class,
376                                    "LBL_EditorName", //NOI18N
377
new Integer JavaDoc(version),
378                                    name);
379     }
380     
381     /**
382      * Overrides superclass abstract method.
383      * Constructs message that should be used to name the editor component.
384      * @return name of the editor
385      */

386     protected String JavaDoc messageName () {
387         if (!myEntry.getDataObject().isValid()) {
388             return ""; //NOI18N
389
}
390         
391         return addModifiedInfo(getRawMessageName());
392     }
393
394     /** */
395     protected String JavaDoc messageHtmlName () {
396         if (!myEntry.getDataObject().isValid()) {
397             return null;
398         }
399
400         String JavaDoc rawName = getRawMessageName();
401         
402         String JavaDoc annotatedName = null;
403         final FileObject entry = myEntry.getFile();
404         try {
405             FileSystem.Status status = entry.getFileSystem().getStatus();
406             if (status != null) {
407                 Set JavaDoc<FileObject> files = Collections.singleton(entry);
408                 if (status instanceof FileSystem.HtmlStatus) {
409                     FileSystem.HtmlStatus hStatus = (FileSystem.HtmlStatus)
410                                                     status;
411                     annotatedName = hStatus.annotateNameHtml(rawName, files);
412                     if (rawName.equals(annotatedName)) {
413                         annotatedName = null;
414                     }
415                     if ((annotatedName != null)
416                             && (!annotatedName.startsWith("<html>"))) { //NOI18N
417
annotatedName = "<html>" + annotatedName; //NOI18N
418
}
419                 }
420                 if (annotatedName == null) {
421                     annotatedName = status.annotateName(rawName, files);
422                 }
423             }
424         } catch (FileStateInvalidException ex) {
425             //do nothing and fall through
426
}
427         
428         String JavaDoc name = (annotatedName != null) ? annotatedName : rawName;
429         return addModifiedInfo(name);
430     }
431     
432     /**
433      * Overrides superclass abstract method.
434      * Is modified and is being closed.
435      * @return text to show to the user
436      */

437     protected String JavaDoc messageSave () {
438         String JavaDoc name = myEntry.getDataObject().getPrimaryFile().getName()+"("+Util.getLocaleLabel(myEntry)+")"; // NOI18N
439

440         return NbBundle.getMessage (
441             PropertiesEditorSupport.class,
442             "MSG_SaveFile", // NOI18N
443
name
444         );
445     }
446     
447     /**
448      * Overrides superclass abstract method.
449      * Text to use as tooltip for component.
450      * @return text to show to the user
451      */

452     protected String JavaDoc messageToolTip () {
453         // copied from DataEditorSupport, more or less
454
FileObject fo = myEntry.getFile();
455         return FileUtil.getFileDisplayName(fo);
456     }
457     
458     /** Overrides superclass method. Gets <code>UndoRedo</code> manager which maps
459      * <code>UndoalbleEdit</code>'s to <code>StampFlag</code>'s. */

460     protected UndoRedo.Manager createUndoRedoManager () {
461         return new UndoRedoStampFlagManager();
462     }
463     
464     /**
465      * Helper method. Hack on superclass <code>getUndoRedo()</code> method, to widen its protected modifier.
466      * Needs to be accessed from outside this class (in <code>PropertiesOpen</code>).
467      * @see PropertiesOpen
468      */

469     UndoRedo.Manager getUndoRedoManager() {
470         return super.getUndoRedo();
471     }
472     
473     /**
474      * Helper method. Used only by <code>PropertiesOpen</code> support when closing last Table component.
475      * Note: It's quite ugly by-pass of <code>notifyClosed()</code> method. Should be revised.
476      */

477     void forceNotifyClosed() {
478         super.notifyClosed();
479     }
480     
481     /** Helper method.
482      * @return <code>newLineType</code>.*/

483     byte getNewLineType() {
484         return newLineType;
485     }
486     
487     /** Helper method. Saves this entry. */
488     private void saveThisEntry() throws IOException {
489         super.saveDocument();
490         // #32777 - it can happen that save operation was interrupted
491
// and file is still modified. Mark it unmodified only when it is really
492
// not modified.
493
if (!env.isModified()) {
494             myEntry.setModified(false);
495         }
496     }
497     
498     /** Helper method.
499      * @return whether there is an table view opened */

500     public synchronized boolean hasOpenedTableComponent() {
501         return ((PropertiesDataObject)myEntry.getDataObject()).getOpenSupport().hasOpenedTableComponent();
502     }
503     
504     /**
505      * Helper method.
506      * @return whether there is an open editor component. */

507     public synchronized boolean hasOpenedEditorComponent() {
508         Enumeration JavaDoc en = allEditors.getComponents ();
509         return en.hasMoreElements ();
510     }
511
512     /** Class which exist only due comaptibility with version 3.0. */
513     private static final class Env extends Environment {
514         /** Generated Serialized Version UID. */
515         static final long serialVersionUID = -9218186467757330339L;
516
517         /** Used for deserialization. */
518         private PropertiesFileEntry entry;
519
520         /** */
521         public Env(PropertiesFileEntry entry) {
522             super(entry);
523         }
524
525         /** Adds passing entry field to superclass. */
526         private void readObject(java.io.ObjectInputStream JavaDoc in)
527             throws IOException, ClassNotFoundException JavaDoc {
528                 in.defaultReadObject();
529                 
530                 if(this.entry != null)
531                     super.entry = this.entry;
532         }
533     }
534
535     
536     /** Nested class. Implementation of <code>ClonableEditorSupport.Env</code> interface. */
537     private static class Environment implements CloneableEditorSupport.Env,
538     PropertyChangeListener, SaveCookie {
539         
540         /** generated Serialized Version UID */
541         static final long serialVersionUID = 354528097109874355L;
542             
543         /** Entry on which is support build. */
544         protected PropertiesFileEntry entry;
545             
546         /** Lock acquired after the first modification and used in <code>save</code> method. */
547         private transient FileLock fileLock;
548             
549         /** Spport for firing of property changes. */
550         private transient PropertyChangeSupport propSupp;
551         
552         /** Support for firing of vetoable changes. */
553         private transient VetoableChangeSupport vetoSupp;
554             
555             
556         /** Constructor.
557          * @param obj this support should be associated with
558          */

559         public Environment (PropertiesFileEntry entry) {
560             this.entry = entry;
561             entry.getFile().addFileChangeListener(new EnvironmentListener(this));
562             entry.addPropertyChangeListener(this);
563         }
564
565         /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Adds property listener. */
566         public void addPropertyChangeListener(PropertyChangeListener l) {
567             prop().addPropertyChangeListener (l);
568         }
569
570         
571         /** Accepts property changes from entry and fires them to own listeners. */
572         public void propertyChange(PropertyChangeEvent evt) {
573             // We will handle the object invalidation here.
574
if(DataObject.PROP_VALID.equals(evt.getPropertyName ())) {
575                 // do not check it if old value is not true
576
if(Boolean.FALSE.equals(evt.getOldValue())) return;
577
578                 // loosing validity
579
PropertiesEditorSupport support = (PropertiesEditorSupport)findCloneableOpenSupport();
580                 if(support != null) {
581                     
582                     // mark the object as not being modified, so nobody
583
// will ask for save
584
unmarkModified();
585
586                     support.close(false);
587                 }
588             } else {
589                 firePropertyChange (
590                     evt.getPropertyName(),
591                     evt.getOldValue(),
592                     evt.getNewValue()
593                 );
594             }
595         }
596         
597         /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Removes property listener. */
598         public void removePropertyChangeListener(PropertyChangeListener l) {
599             prop().removePropertyChangeListener (l);
600         }
601             
602         /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Adds veto listener. */
603         public void addVetoableChangeListener(VetoableChangeListener l) {
604             veto().addVetoableChangeListener (l);
605         }
606             
607         /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Removes veto listener. */
608         public void removeVetoableChangeListener(VetoableChangeListener l) {
609             veto().removeVetoableChangeListener (l);
610         }
611
612         /** Overrides superclass method.
613          * Note: in fact it returns <code>CloneableEditorSupport</code> instance.
614          * @return the support or null if the environemnt is not in valid
615          * state and the CloneableOpenSupport cannot be found for associated
616          * entry object
617          */

618         public CloneableOpenSupport findCloneableOpenSupport() {
619             return (PropertiesEditorSupport)entry.getCookieSet().getCookie(EditCookie.class);
620         }
621
622         /**
623          * Implements <code>CloneableEditorSupport.Env</code> interface.
624          * Test whether the support is in valid state or not.
625          * It could be invalid after deserialization when the object it
626          * referenced to does not exist anymore.
627          * @return true or false depending on its state
628          */

629         public boolean isValid() {
630             return entry.getDataObject().isValid();
631         }
632             
633         /**
634          * Implements <code>CloneableEditorSupport.Env</code> interface.
635          * Test whether the object is modified or not.
636          * @return true if the object is modified
637          */

638         public boolean isModified() {
639             return entry.isModified();
640         }
641
642         /**
643          * Implements <code>CloneableEditorSupport.Env</code> interface.
644          * First of all tries to lock the primary file and
645          * if it succeeds it marks the data object modified.
646          * @exception IOException if the environment cannot be marked modified
647          * (for example when the file is readonly), when such exception
648          * is the support should discard all previous changes
649          */

650         public void markModified() throws java.io.IOException JavaDoc {
651             if (fileLock == null || !fileLock.isValid()) {
652                 fileLock = entry.takeLock();
653             }
654             
655             entry.setModified(true);
656         }
657             
658         /**
659          * Implements <code>CloneableEditorSupport.Env</code> interface.
660          * Reverse method that can be called to make the environment
661          * unmodified.
662          */

663         public void unmarkModified() {
664             if (fileLock != null && fileLock.isValid()) {
665                 fileLock.releaseLock();
666             }
667             
668             entry.setModified(false);
669         }
670
671         /**
672          * Implements <code>CloneableEditorSupport.Env</code> interface.
673          * Mime type of the document.
674          * @return the mime type to use for the document
675          */

676         public String JavaDoc getMimeType() {
677             return entry.getFile().getMIMEType();
678         }
679             
680         /**
681          * Implements <code>CloneableEditorSupport.Env</code> interface.
682          * The time when the data has been modified. */

683         public Date JavaDoc getTime() {
684             // #32777 - refresh file object and return always the actual time
685
entry.getFile().refresh(false);
686             return entry.getFile().lastModified();
687         }
688             
689         /**
690          * Implements <code>CloneableEditorSupport.Env</code> interface.
691          * Obtains the input stream.
692          * @exception IOException if an I/O error occures
693          */

694         public InputStream inputStream() throws IOException {
695             return entry.getFile().getInputStream();
696         }
697             
698         /**
699          * Implements <code>CloneableEditorSupport.Env</code> interface.
700          * Obtains the output stream.
701          * @exception IOException if an I/O error occures
702          */

703         public OutputStream outputStream() throws IOException {
704             return entry.getFile().getOutputStream(fileLock);
705         }
706
707         /**
708          * Implements <code>SaveCookie</code> interface.
709          * Invoke the save operation.
710          * @throws IOException if the object could not be saved
711          */

712         public void save() throws IOException {
713             // Do saving job. Note it gets editor support, not open support.
714
((PropertiesEditorSupport)findCloneableOpenSupport()).saveThisEntry();
715         }
716             
717         /** Fires property change.
718          * @param name the name of property that changed
719          * @param oldValue old value
720          * @param newValue new value
721          */

722         private void firePropertyChange (String JavaDoc name, Object JavaDoc oldValue, Object JavaDoc newValue) {
723             prop().firePropertyChange (name, oldValue, newValue);
724         }
725             
726         /** Fires vetoable change.
727          * @param name the name of property that changed
728          * @param oldValue old value
729          * @param newValue new value
730          */

731         private void fireVetoableChange (String JavaDoc name, Object JavaDoc oldValue, Object JavaDoc newValue) throws PropertyVetoException {
732                 veto ().fireVetoableChange (name, oldValue, newValue);
733         }
734             
735         /** Lazy getter for property change support. */
736         private PropertyChangeSupport prop() {
737             if (propSupp == null) {
738                 synchronized (this) {
739                     if (propSupp == null) {
740                         propSupp = new PropertyChangeSupport (this);
741                     }
742                 }
743             }
744             
745             return propSupp;
746         }
747             
748         /** Lazy getter for vetoable support. */
749         private VetoableChangeSupport veto() {
750             if (vetoSupp == null) {
751                 synchronized (this) {
752                     if (vetoSupp == null) {
753                         vetoSupp = new VetoableChangeSupport (this);
754                     }
755                 }
756             }
757             return vetoSupp;
758         }
759             
760         /** Helper method. Adds save cookie to the entry. */
761         private void addSaveCookie() {
762             if (entry.getCookie(SaveCookie.class) == null) {
763                 entry.getCookieSet().add(this);
764             }
765             ((PropertiesDataObject)entry.getDataObject()).updateModificationStatus();
766         }
767             
768         /** Helper method. Removes save cookie from the entry. */
769         private void removeSaveCookie() {
770             // remove Save cookie from the entry
771
SaveCookie sc = (SaveCookie)entry.getCookie(SaveCookie.class);
772             
773             if (sc != null && sc.equals(this)) {
774                 entry.getCookieSet().remove(this);
775             }
776             
777             PropertiesRequestProcessor.getInstance().post(new Runnable JavaDoc() {
778                 public void run() {
779                     ((PropertiesDataObject)entry.getDataObject()).updateModificationStatus();
780                 }
781             });
782         }
783             
784         /** Called from the <code>EnvironmnetListener</code>
785          * @param expected is the change expected
786          * @param time of the change
787          */

788         private void fileChanged(boolean expected, long time) {
789             if (expected) {
790                 // newValue = null means do not ask user whether to reload
791
firePropertyChange (PROP_TIME, null, null);
792             } else {
793                 firePropertyChange (PROP_TIME, null, new Date JavaDoc (time));
794             }
795         }
796
797         
798         /** Called from the <code>EnvironmentListener</code>.
799          * The components are going to be closed anyway and in case of
800          * modified document its asked before if to save the change. */

801         private void fileRemoved() {
802             try {
803                 fireVetoableChange(PROP_VALID, Boolean.TRUE, Boolean.FALSE);
804             } catch(PropertyVetoException pve) {
805                 // Ignore it and close anyway. File doesn't exist anymore.
806
}
807             
808             firePropertyChange(PROP_VALID, Boolean.TRUE, Boolean.FALSE);
809         }
810     } // End of nested class Environment.
811

812     
813     /** Weak listener on file object that notifies the <code>Environment</code> object
814      * that a file has been modified. */

815     private static final class EnvironmentListener extends FileChangeAdapter {
816         
817         /** Reference of <code>Environment</code> */
818         private Reference JavaDoc<Environment> reference;
819         
820         /** @param environment <code>Environment<code> to use
821          */

822         public EnvironmentListener(Environment environment) {
823             reference = new WeakReference JavaDoc<Environment>(environment);
824         }
825         
826         /** Fired when a file is changed.
827          * @param fe the event describing context where action has taken place
828          */

829         public void fileChanged(FileEvent evt) {
830             Environment environment = reference.get();
831             if (environment != null) {
832                 if(!environment.entry.getFile().equals(evt.getFile()) ) {
833                     // If the FileObject was changed.
834
// Remove old listener from old FileObject.
835
evt.getFile().removeFileChangeListener(this);
836                     // Add new listener to new FileObject.
837
environment.entry.getFile().addFileChangeListener(new EnvironmentListener(environment));
838                     return;
839                 }
840
841                 // #16403. See DataEditorSupport.EnvListener.
842
if(evt.getFile().isVirtual()) {
843                     environment.entry.getFile().removeFileChangeListener(this);
844                     // File doesn't exist on disk -> simulate env is invalid,
845
// even the fileObject could be valid, see VCS FS.
846
environment.fileRemoved();
847                     environment.entry.getFile().addFileChangeListener(this);
848                 } else {
849                     environment.fileChanged(evt.isExpected(), evt.getTime());
850                 }
851             }
852         }
853     } // End of nested class EnvironmentListener.
854

855     
856     /** Inner class for opening editor view at a given key. */
857     public class PropertiesEditAt implements EditCookie {
858
859         /** Key at which should be pane opened. (Cursor will be at the position of that key). */
860         private String JavaDoc key;
861         
862         
863         /** Constructor. */
864         PropertiesEditAt(String JavaDoc key) {
865             this.key = key;
866         }
867         
868         
869         /** Setter for <code>key</code>. */
870         public void setKey(String JavaDoc key) {
871             this.key = key;
872         }
873         
874         /** Implementation of <code>EditCookie</code> interface. */
875         public void edit() {
876             PropertiesEditor editor = (PropertiesEditor)PropertiesEditorSupport.super.openCloneableTopComponent();
877             editor.requestActive();
878             
879             Element.ItemElem item = myEntry.getHandler().getStructure().getItem(key);
880             if (item != null) {
881                 int offset = item.getKeyElem().getBounds().getBegin().getOffset();
882                 if (editor.getPane() != null && editor.getPane().getCaret() !=null)
883                     editor.getPane().getCaret().setDot(offset);
884             }
885         }
886     } // End of inner class PropertiesEditAt.
887

888     
889     /** Cloneable top component to hold the editor kit. */
890     public static class PropertiesEditor extends CloneableEditor
891                                          implements Runnable JavaDoc {
892         
893         /** Holds the file being edited. */
894         protected transient PropertiesFileEntry entry;
895         
896         /** Listener for entry's save cookie changes. */
897         private transient PropertyChangeListener saveCookieLNode;
898         
899         /** */
900         private transient boolean hasBeenActivated = false;
901         
902         /** Generated serial version UID. */
903         static final long serialVersionUID =-2702087884943509637L;
904         
905         
906         /** Constructor for deserialization */
907         public PropertiesEditor() {
908             super();
909         }
910         
911         /** Creates new editor */
912         public PropertiesEditor(PropertiesEditorSupport support) {
913             super(support);
914         }
915
916         
917         /** Initializes object, used in construction and deserialization. */
918         private void initialize(PropertiesFileEntry entry) {
919             this.entry = entry;
920             
921             Node n = entry.getNodeDelegate ();
922             setActivatedNodes (new Node[] { n });
923             
924             updateName();
925             
926             // entry to the set of listeners
927
saveCookieLNode = new PropertyChangeListener() {
928                 public void propertyChange(PropertyChangeEvent evt) {
929                     if (Node.PROP_COOKIE.equals(evt.getPropertyName()) ||
930                     DataObject.PROP_NAME.equals(evt.getPropertyName())) {
931                         PropertiesEditor.super.updateName();
932                     }
933                 }
934             };
935             this.entry.addPropertyChangeListener(
936             WeakListeners.propertyChange(saveCookieLNode, this.entry));
937         }
938         
939         /**
940          */

941         protected void componentActivated() {
942             super.componentActivated();
943             if (!hasBeenActivated) {
944                 hasBeenActivated = true;
945                 if (Boolean.TRUE.equals(getEditorPane().getDocument().getProperty(
946                                                        PROP_NON_ASCII_CHAR_READ))) {
947                     EventQueue.invokeLater(this);
948                 }
949             }
950         }
951         
952         /**
953          */

954         public void run() {
955             String JavaDoc msg = NbBundle.getMessage(getClass(),
956                                              "MSG_OnlyLatin1Supported");//NOI18N
957
Object JavaDoc msgObject;
958             int nlIndex = msg.indexOf('\n');
959             if (nlIndex == -1) {
960                 msgObject = msg;
961             } else {
962                 StringBuilder JavaDoc buf = new StringBuilder JavaDoc(msg.length() + 40);
963                 buf.append("<html>"); //NOI18N
964

965                 int lastNlIndex = -1;
966                 do {
967                     buf.append(msg.substring(lastNlIndex + 1, nlIndex));
968                     buf.append("<br>"); //NOI18N
969
lastNlIndex = nlIndex;
970                     nlIndex = msg.indexOf('\n', lastNlIndex + 1);
971                 } while (nlIndex != -1);
972                 buf.append(msg.substring(lastNlIndex + 1));
973                 msgObject = new JLabel JavaDoc(buf.toString());
974             }
975             
976             DialogDisplayer.getDefault().notify(
977                     new NotifyDescriptor.Message(
978                             msgObject,
979                             NotifyDescriptor.WARNING_MESSAGE));
980         }
981
982         /**
983          * Overrides superclass method.
984          * When closing last view, also close the document.
985          * @return <code>true</code> if close succeeded
986          */

987         protected boolean closeLast () {
988             return super.closeLast();
989         }
990
991         /** Overrides superclass method. Gets <code>Icon</code>. */
992         public Image JavaDoc getIcon () {
993             return Utilities.loadImage("org/netbeans/modules/properties/propertiesLocale.gif"); // NOI18N
994
}
995         
996         /** Overrides superclass method. Gets help context. */
997         public HelpCtx getHelpCtx() {
998             return new HelpCtx(Util.HELP_ID_EDITLOCALE);
999         }
1000        
1001        /** Getter for pane. */
1002        private JEditorPane JavaDoc getPane() {
1003            return pane;
1004        }
1005    } // End of nested class PropertiesEditor.
1006

1007
1008    /** This stream is able to filter various new line delimiters and replace them by \n. */
1009    static class NewLineReader extends BufferedReader {
1010        
1011        /** The count of types new line delimiters used in the file */
1012        int[] newLineTypes;
1013        /** */
1014        private boolean nonAsciiCharacterRead = false;
1015        
1016        
1017        /** Creates new stream.
1018         * @param is encapsulated input stream.
1019         * @param justFilter The flag determining if this stream should
1020         * store the guarded block information. True means just filter,
1021         * false means store the information.
1022         */

1023        public NewLineReader(InputStream is) throws IOException {
1024            super(new InputStreamReader(is, "8859_1")); // NOI18N
1025

1026            newLineTypes = new int[] { 0, 0, 0 };
1027        }
1028
1029        
1030        /** Overrides superclass method. Reads one character.
1031         * @return next char or -1 if the end of file was reached.
1032         * @exception IOException if any problem occured.
1033         */

1034        public int read() throws IOException {
1035            int nextToRead = super.read();
1036            
1037            if (nextToRead == -1)
1038                return -1;
1039            
1040            if (nextToRead == '\r') {
1041                nextToRead = super.read();
1042                
1043                while (nextToRead == '\r')
1044                    nextToRead = super.read();
1045                if (nextToRead == '\n') {
1046                    nextToRead = super.read();
1047                    newLineTypes[NEW_LINE_RN]++;
1048                    return '\n';
1049                } else {
1050                    newLineTypes[NEW_LINE_R]++;
1051                    return '\n';
1052                }
1053            }
1054            if (nextToRead == '\n') {
1055                nextToRead = super.read();
1056                newLineTypes[NEW_LINE_N]++;
1057                return '\n';
1058            }
1059            if (nextToRead > 0x7e) {
1060                nonAsciiCharacterRead = true;
1061            }
1062            
1063            return nextToRead;
1064        }
1065
1066        /**
1067         * {@inheritdoc}
1068         */

1069        public int read(char cbuf[], int off, int len) throws IOException {
1070            int charsCount = super.read(cbuf, off, len);
1071            if (!nonAsciiCharacterRead && (charsCount > 0)) {
1072                final int upperLimit = off + len;
1073                for (int i = off; i < upperLimit; i++) {
1074                    if (cbuf[i] > 0x7e) {
1075                        nonAsciiCharacterRead = true;
1076                        break;
1077                    }
1078                }
1079            }
1080            return charsCount;
1081        }
1082        
1083        /** Gets new line type. */
1084        public byte getNewLineType() {
1085            if (newLineTypes[0] > newLineTypes[1]) {
1086                return (newLineTypes[0] > newLineTypes[2]) ? NEW_LINE_N : NEW_LINE_RN;
1087            } else {
1088                return (newLineTypes[1] > newLineTypes[2]) ? NEW_LINE_R : NEW_LINE_RN;
1089            }
1090        }
1091        
1092        /**
1093         */

1094        public boolean wasNonAsciiCharacterRead() {
1095            return nonAsciiCharacterRead;
1096        }
1097        
1098    } // End of nested class NewLineReader.
1099

1100    
1101    /** This stream is used for changing the new line delimiters.
1102     * Replaces the '\n' by '\n', '\r' or "\r\n". */

1103    static class NewLineWriter extends BufferedWriter {
1104        
1105        /** The type of new line delimiter */
1106        byte newLineType;
1107
1108        
1109        /** Creates new stream.
1110         * @param stream Underlaying stream
1111         * @param newLineType The type of new line delimiter
1112         */

1113        public NewLineWriter(OutputStream stream, byte newLineType) throws UnsupportedEncodingException {
1114            super(new OutputStreamWriter(stream, "8859_1"));
1115            
1116            this.newLineType = newLineType;
1117        }
1118        
1119        
1120        /** Write one character. New line char replaces according the <code>newLineType</code> character.
1121         * @param character character to write.
1122         */

1123        public void write(int character) throws IOException {
1124            if(character == '\r')
1125                // Do nothing.
1126
return;
1127            if(character == '\n') {
1128                if(newLineType == NEW_LINE_R) {
1129                    // Replace new line by \r.
1130
super.write('\r');
1131                } else if(newLineType == NEW_LINE_N) {
1132                    // Replace new line by \n.
1133
super.write('\n');
1134                } else if(newLineType == NEW_LINE_RN) {
1135                    // Replace new line by \r\n.
1136
super.write('\r');
1137                    super.write('\n');
1138                }
1139            } else {
1140                super.write(character);
1141            }
1142        }
1143        
1144    } // End of nested class NewLineWriter.
1145

1146    
1147    /** Inner class. UndoRedo manager which saves a StampFlag
1148     * for each UndoAbleEdit.
1149     */

1150    class UndoRedoStampFlagManager extends UndoRedo.Manager {
1151        
1152        /** Hash map of weak reference keys (UndoableEdit's) to their StampFlag's. */
1153        WeakHashMap JavaDoc<UndoableEdit JavaDoc,StampFlag> stampFlags
1154                = new WeakHashMap JavaDoc<UndoableEdit JavaDoc,StampFlag>(5);
1155        
1156        /** Overrides superclass method. Adds StampFlag to UndoableEdit. */
1157        public synchronized boolean addEdit(UndoableEdit JavaDoc anEdit) {
1158            stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(),
1159                ((PropertiesDataObject)PropertiesEditorSupport.this.myEntry.getDataObject()).getOpenSupport().atomicUndoRedoFlag ));
1160            return super.addEdit(anEdit);
1161        }
1162        
1163        /** Overrides superclass method. Adds StampFlag to UndoableEdit. */
1164        public boolean replaceEdit(UndoableEdit JavaDoc anEdit) {
1165            stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(),
1166                ((PropertiesDataObject)PropertiesEditorSupport.this.myEntry.getDataObject()).getOpenSupport().atomicUndoRedoFlag ));
1167            return super.replaceEdit(anEdit);
1168        }
1169        
1170        /** Overrides superclass method. Updates time stamp for the edit. */
1171        public synchronized void undo() throws CannotUndoException JavaDoc {
1172            UndoableEdit JavaDoc anEdit = editToBeUndone();
1173            if(anEdit != null) {
1174                Object JavaDoc atomicFlag = stampFlags.get(anEdit).getAtomicFlag(); // atomic flag remains
1175
super.undo();
1176                stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), atomicFlag));
1177            }
1178        }
1179        
1180        /** Overrides superclass method. Updates time stamp for that edit. */
1181        public synchronized void redo() throws CannotRedoException JavaDoc {
1182            UndoableEdit JavaDoc anEdit = editToBeRedone();
1183            if(anEdit != null) {
1184                Object JavaDoc atomicFlag = stampFlags.get(anEdit).getAtomicFlag(); // atomic flag remains
1185
super.redo();
1186                stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), atomicFlag));
1187            }
1188        }
1189        
1190        /** Method which gets time stamp of next Undoable edit to be undone.
1191         * @ return time stamp in milliseconds or 0 (if don't exit edit to be undone). */

1192        public long getTimeStampOfEditToBeUndone() {
1193            UndoableEdit JavaDoc nextUndo = editToBeUndone();
1194            if (nextUndo == null) {
1195                return 0L;
1196            } else {
1197                return stampFlags.get(nextUndo).getTimeStamp();
1198            }
1199        }
1200        
1201        /** Method which gets time stamp of next Undoable edit to be redone.
1202         * @ return time stamp in milliseconds or 0 (if don't exit edit to be redone). */

1203        public long getTimeStampOfEditToBeRedone() {
1204            UndoableEdit JavaDoc nextRedo = editToBeRedone();
1205            if (nextRedo == null) {
1206                return 0L;
1207            } else {
1208                return stampFlags.get(nextRedo).getTimeStamp();
1209            }
1210        }
1211        
1212        /** Method which gets atomic flag of next Undoable edit to be undone.
1213         * @ return atomic flag in milliseconds or 0 (if don't exit edit to be undone). */

1214        public Object JavaDoc getAtomicFlagOfEditToBeUndone() {
1215            UndoableEdit JavaDoc nextUndo = editToBeUndone();
1216            if (nextUndo == null) {
1217                return null;
1218            } else {
1219                return (stampFlags.get(nextUndo)).getAtomicFlag();
1220            }
1221        }
1222        
1223        /** Method which gets atomic flag of next Undoable edit to be redone.
1224         * @ return time stamp in milliseconds or 0 (if don't exit edit to be redone). */

1225        public Object JavaDoc getAtomicFlagOfEditToBeRedone() {
1226            UndoableEdit JavaDoc nextRedo = editToBeRedone();
1227            if (nextRedo == null) {
1228                return null;
1229            } else {
1230                return (stampFlags.get(nextRedo)).getAtomicFlag();
1231            }
1232        }
1233        
1234    } // End of inner class UndoRedoTimeStampManager.
1235

1236    /** Simple nested class for storing time stamp and atomic flag used
1237     * in <code>UndoRedoStampFlagManager</code>.
1238     */

1239    static class StampFlag {
1240        
1241        /** Time stamp when was an UndoableEdit (to which is this class mapped via
1242         * UndoRedoStampFlagManager,) was created, replaced, undone, or redone. */

1243        private long timeStamp;
1244        
1245        /** Atomic flag. If this object is not null it means that an UndoableEdit ( to which
1246         * is this class mapped via UndoRedoStampFlagManager,) was created as part of one
1247         * action which could consist from more UndoableEdits in differrent editor supports.
1248         * These Undoable edits are marked with this (i.e. same) object. */

1249        private Object JavaDoc atomicFlag;
1250        
1251        /** Consructor. */
1252        public StampFlag(long timeStamp, Object JavaDoc atomicFlag) {
1253            this.timeStamp = timeStamp;
1254            this.atomicFlag = atomicFlag;
1255        }
1256        
1257        /** Getter for time stamp. */
1258        public long getTimeStamp() {
1259            return timeStamp;
1260        }
1261        
1262        /** Setter for time stamp. */
1263        public void setTimeStamp(long timeStamp) {
1264            this.timeStamp = timeStamp;
1265        }
1266        
1267        /** Getter for atomic flag.
1268         @ return Returns null if is not linked with more Undoable edits.*/

1269        public Object JavaDoc getAtomicFlag() {
1270            return atomicFlag;
1271        }
1272    } // End of nested class TimeStamp.
1273
}
1274
Popular Tags