| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.properties; |
| |
| import java.awt.event.ActionEvent; |
| import javax.swing.AbstractAction; |
| import javax.swing.Action; |
| import org.netbeans.core.spi.multiview.MultiViewFactory; |
| import org.openide.util.Exceptions; |
| import org.openide.util.lookup.Lookups; |
| import org.openide.util.Lookup; |
| import javax.swing.JComponent; |
| import javax.swing.JToolBar; |
| import org.netbeans.core.spi.multiview.CloseOperationState; |
| import org.netbeans.core.spi.multiview.MultiViewElementCallback; |
| import org.openide.text.NbDocument; |
| import org.netbeans.core.spi.multiview.MultiViewElement; |
| import org.openide.util.NbBundle.Messages; |
| import org.netbeans.core.api.multiview.MultiViews; |
| import java.awt.EventQueue; |
| import java.awt.Image; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.beans.PropertyVetoException; |
| import java.beans.VetoableChangeListener; |
| import java.beans.VetoableChangeSupport; |
| import java.io.BufferedOutputStream; |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Reader; |
| import java.io.Serializable; |
| import java.io.Writer; |
| import java.lang.ref.Reference; |
| import java.lang.ref.WeakReference; |
| import java.nio.charset.Charset; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.JEditorPane; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.EditorKit; |
| import javax.swing.text.StyledDocument; |
| import javax.swing.undo.CannotRedoException; |
| import javax.swing.undo.CannotUndoException; |
| import javax.swing.undo.UndoableEdit; |
| import org.netbeans.api.queries.FileEncodingQuery; |
| import org.netbeans.modules.properties.PropertiesEncoding.PropCharset; |
| import org.netbeans.modules.properties.PropertiesEncoding.PropCharsetEncoder; |
| import org.openide.ErrorManager; |
| import org.openide.awt.UndoRedo; |
| import org.openide.cookies.CloseCookie; |
| import org.openide.cookies.EditCookie; |
| import org.openide.cookies.EditorCookie; |
| import org.openide.cookies.OpenCookie; |
| import org.openide.cookies.PrintCookie; |
| import org.openide.cookies.SaveCookie; |
| import org.openide.filesystems.FileChangeAdapter; |
| import org.openide.filesystems.FileEvent; |
| import org.openide.filesystems.FileLock; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileRenameEvent; |
| import org.openide.filesystems.FileStatusEvent; |
| import org.openide.filesystems.FileStatusListener; |
| import org.openide.filesystems.FileSystem; |
| import org.openide.filesystems.FileStateInvalidException; |
| import org.openide.filesystems.FileSystem.AtomicAction; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.loaders.DataFolder; |
| import org.openide.loaders.DataObject; |
| import org.openide.loaders.SaveAsCapable; |
| import org.openide.nodes.Node; |
| import org.openide.text.CloneableEditor; |
| import org.openide.text.CloneableEditorSupport; |
| import org.openide.text.DataEditorSupport; |
| import org.openide.util.HelpCtx; |
| import org.openide.util.ImageUtilities; |
| import org.openide.util.Mutex; |
| import org.openide.util.NbBundle; |
| import org.openide.util.WeakListeners; |
| import org.openide.windows.CloneableOpenSupport; |
| import org.openide.util.Task; |
| import org.openide.util.TaskListener; |
| import org.openide.util.lookup.ProxyLookup; |
| import org.openide.windows.CloneableTopComponent; |
| import org.openide.windows.TopComponent; |
| import static java.util.logging.Level.FINER; |
| import javax.swing.*; |
| import org.openide.filesystems.StatusDecorator; |
| |
| /** |
| * Support for viewing .properties files (EditCookie) by opening them in a text editor. |
| * |
| * @author Petr Jiricka, Peter Zavadsky |
| * @see org.openide.text.CloneableEditorSupport |
| */ |
| public class PropertiesEditorSupport extends CloneableEditorSupport |
| implements EditCookie, EditorCookie.Observable, PrintCookie, CloseCookie, Serializable, SaveAsCapable { |
| |
| /** error manager for CloneableEditorSupport logging and error reporting */ |
| static final Logger LOG = Logger.getLogger("org.netbeans.modules.properties.PropertiesEditorSupport"); // NOI18N |
| |
| /** */ |
| private FileStatusListener fsStatusListener; |
| |
| /** Visible view of underlying file entry */ |
| transient PropertiesFileEntry myEntry; |
| |
| /** Generated serial version UID. */ |
| static final long serialVersionUID =1787354011149868490L; |
| |
| private Charset charset; |
| |
| /** Constructor. */ |
| public PropertiesEditorSupport(PropertiesFileEntry entry) { |
| super(new Environment(entry), new ProxyLookup(Lookups.singleton(entry.getDataObject()), entry.getDataObject().getLookup())); |
| this.myEntry = entry; |
| } |
| |
| @Override |
| protected Pane createPane() { |
| return (Pane) MultiViews.createCloneableMultiView(PropertiesDataLoader.PROPERTIES_MIME_TYPE, getDataObject()); |
| } |
| |
| /** Getter for the environment that was provided in the constructor. |
| * @return the environment |
| */ |
| final CloneableEditorSupport.Env desEnv() { |
| return (CloneableEditorSupport.Env) env; |
| } |
| |
| /** |
| * Overrides superclass method. |
| * Should test whether all data is saved, and if not, prompt the user |
| * to save. Called by my topcomponent when it wants to close its last topcomponent, but the table editor may still be open |
| * @return <code>true</code> if everything can be closed |
| */ |
| @Override |
| protected boolean canClose () { |
| // if the table is open, can close without worries, don't remove the save cookie |
| if (hasOpenedTableComponent()){ |
| return true; |
| }else{ |
| DataObject propDO = myEntry.getDataObject(); |
| if ((propDO == null) || !propDO.isModified()) { |
| return true; |
| } |
| return super.canClose(); |
| } |
| } |
| |
| /** Getter of the data object that this support is associated with. |
| * @return data object passed in constructor |
| */ |
| public final DataObject getDataObject () { |
| return myEntry.getDataObject(); |
| } |
| |
| private boolean isEnvReadOnly() { |
| CloneableEditorSupport.Env myEnv = desEnv(); |
| return myEnv instanceof Environment && !((Environment) myEnv).getFileImpl().canWrite(); |
| } |
| |
| /** |
| * |
| */ |
| final class FsStatusListener implements FileStatusListener, Runnable { |
| |
| /** |
| */ |
| public void annotationChanged(FileStatusEvent ev) { |
| if (ev.isNameChange() && ev.hasChanged(myEntry.getFile())) { |
| Mutex.EVENT.writeAccess(this); |
| } |
| } |
| |
| /** |
| */ |
| public void run() { |
| updateEditorDisplayNames(); |
| } |
| } |
| |
| /** |
| */ |
| private void attachStatusListener() { |
| if (fsStatusListener != null) { |
| return; //already attached |
| } |
| |
| FileSystem fs; |
| try { |
| fs = myEntry.getFile().getFileSystem(); |
| } catch (FileStateInvalidException ex) { |
| ErrorManager.getDefault().notify(ErrorManager.ERROR, ex); |
| return; |
| } |
| |
| fsStatusListener = new FsStatusListener(); |
| fs.addFileStatusListener( |
| FileUtil.weakFileStatusListener(fsStatusListener, fs)); |
| } |
| |
| /** |
| */ |
| private void updateEditorDisplayNames() { |
| assert EventQueue.isDispatchThread(); |
| |
| final String title = messageName(); |
| final String htmlTitle = messageHtmlName(); |
| final String toolTip = messageToolTip(); |
| Enumeration en = allEditors.getComponents(); |
| while (en.hasMoreElements()) { |
| TopComponent tc = (TopComponent) en.nextElement(); |
| tc.setDisplayName(title); |
| tc.setHtmlDisplayName(htmlTitle); |
| tc.setToolTipText(toolTip); |
| } |
| } |
| |
| /** |
| */ |
| @Override |
| protected void initializeCloneableEditor(CloneableEditor editor) { |
| ((PropertiesEditor) editor).initialize(myEntry); |
| } |
| |
| /** |
| * Overrides superclass method. |
| * Let's the super method create the document and also annotates it |
| * with Title and StreamDescription properities. |
| * @param kit kit to user to create the document |
| * @return the document annotated by the properties |
| */ |
| @Override |
| protected StyledDocument createStyledDocument(EditorKit kit) { |
| StyledDocument document = super.createStyledDocument(kit); |
| |
| // Set additional proerties to document. |
| // Set document name property. Used in CloneableEditorSupport. |
| document.putProperty(Document.TitleProperty, myEntry.getFile().toString()); |
| |
| // Set dataobject to stream desc property. |
| document.putProperty(Document.StreamDescriptionProperty, myEntry.getDataObject()); |
| |
| // hook the document to listen for any changes to update changes by |
| // reparsing the document |
| document.addDocumentListener(new javax.swing.event.DocumentListener() { |
| public void insertUpdate(javax.swing.event.DocumentEvent e) { changed();} |
| public void changedUpdate(javax.swing.event.DocumentEvent e) { changed();} |
| public void removeUpdate(javax.swing.event.DocumentEvent e) { changed();} |
| private void changed() { |
| myEntry.getHandler().autoParse(); |
| } |
| }); |
| |
| return document; |
| } |
| |
| /** |
| * Reads the file from the stream, filter the guarded section |
| * comments, and mark the sections in the editor. Overrides superclass method. |
| * @param document the document to read into |
| * @param inputStream the open stream to read from |
| * @param editorKit the associated editor kit |
| * @throws <code>IOException</code> if there was a problem reading the file |
| * @throws <code>BadLocationException</code> should not normally be thrown |
| * @see #saveFromKitToStream |
| */ |
| @Override |
| protected void loadFromStreamToKit(StyledDocument document, InputStream inputStream, EditorKit editorKit) throws IOException, BadLocationException { |
| final Charset c = getCharset(); |
| final Reader reader = new BufferedReader(new InputStreamReader(inputStream, c)); |
| |
| try { |
| editorKit.read(reader, document, 0); |
| } finally { |
| reader.close(); |
| } |
| } |
| |
| /** |
| * Adds new lines according actual value of <code>newLineType</code> variable. |
| * Overrides superclass method. |
| * @param document the document to write from |
| * @param editorKit the associated editor kit |
| * @param outputStream the open stream to write to |
| * @throws IOException if there was a problem writing the file |
| * @throws BadLocationException should not normally be thrown |
| * @see #loadFromStreamToKit |
| */ |
| @Override |
| protected void saveFromKitToStream(StyledDocument document, EditorKit editorKit, OutputStream outputStream) throws IOException, BadLocationException { |
| final Writer writer; |
| final Charset c = getCharset(); |
| if (c.name().equals(PropertiesEncoding.PROP_CHARSET_NAME)) { |
| final PropCharsetEncoder encoder = new PropCharsetEncoder(); |
| writer = new BufferedWriter(new OutputStreamWriter(outputStream, encoder)); |
| } else { |
| writer = new BufferedWriter(new OutputStreamWriter(outputStream, c)); |
| } |
| |
| try { |
| editorKit.write(writer, document, 0, document.getLength()); |
| } finally { |
| writer.flush(); |
| writer.close(); |
| } |
| } |
| |
| private Charset getCharset() { |
| if (charset == null) { |
| charset = FileEncodingQuery.getEncoding(myEntry.getDataObject().getPrimaryFile()); |
| } |
| return charset; |
| } |
| |
| void resetCharset() { |
| charset = null; |
| } |
| |
| /** |
| * Adds a save cookie if the document has been marked modified. Overrides superclass method. |
| * @return <code>true</code> if the environment accepted being marked as modified |
| * or <code>false</code> if it refused it and the document should still be unmodified |
| */ |
| @Override |
| protected boolean notifyModified () { |
| // Reparse file. |
| myEntry.getHandler().autoParse(); |
| |
| if (!super.notifyModified()) { |
| return false; // Will cause the log message: |
| // INFO [org.openide.text.ClonableEditorSupport]: ... |
| } |
| |
| // See #89029, #175275, #186876 for info about the implementation |
| |
| ((Environment)env).addSaveCookie(); |
| |
| return true; |
| } |
| |
| @Override |
| protected Task reloadDocument(){ |
| Task tsk = super.reloadDocument(); |
| tsk.addTaskListener(new TaskListener(){ |
| public void taskFinished(Task task){ |
| myEntry.getHandler().autoParse(); |
| } |
| }); |
| return tsk; |
| } |
| |
| /** Overrides superclass method. Adds checking for opened Table component. */ |
| @Override |
| protected void notifyUnmodified () { |
| super.notifyUnmodified(); |
| |
| ((Environment)env).removeSaveCookie(); |
| } |
| |
| /** |
| */ |
| @Override |
| public void open() { |
| super.open(); |
| attachStatusListener(); |
| } |
| |
| /** Overrides superclass method. Adds checking for opened Table panel. */ |
| @Override |
| protected void notifyClosed() { |
| // Close document only in case there is not open table editor. |
| if(!hasOpenedTableComponent()) { |
| boolean wasModified = isModified(); |
| super.notifyClosed(); |
| if (wasModified) { |
| // #21850. Don't reparse invalid or virtual file. |
| if(myEntry.getFile().isValid() && !myEntry.getFile().isVirtual()) { |
| myEntry.getHandler().reparseNowBlocking(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * Message to display when an object is being opened. |
| * @return the message or null if nothing should be displayed |
| */ |
| protected String messageOpening() { |
| return NbBundle.getMessage( |
| PropertiesEditorSupport.class, |
| "LBL_ObjectOpen", // NOI18N |
| getFileLabel() |
| ); |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * Message to display when an object has been opened. |
| * @return the message or null if nothing should be displayed |
| */ |
| protected String messageOpened() { |
| return NbBundle.getMessage( |
| PropertiesEditorSupport.class, |
| "LBL_ObjectOpened", // NOI18N |
| getFileLabel() |
| ); |
| } |
| |
| private String getFileLabel() { |
| PropertiesDataObject propDO = (PropertiesDataObject) myEntry.getDataObject(); |
| return propDO.getPrimaryFile().getNameExt(); |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * Constructs message that should be used to name the editor component. |
| * @return name of the editor |
| */ |
| protected String messageName () { |
| if (!myEntry.getDataObject().isValid()) { |
| return ""; //NOI18N |
| } |
| return DataEditorSupport.annotateName(getFileLabel(), false, isModified(), !myEntry.getFile().canWrite()); |
| } |
| |
| /** */ |
| @Override |
| protected String messageHtmlName () { |
| if (!myEntry.getDataObject().isValid()) { |
| return null; |
| } |
| |
| String rawName = getFileLabel(); |
| |
| String annotatedName = null; |
| final FileObject entry = myEntry.getFile(); |
| try { |
| StatusDecorator status = entry.getFileSystem().getDecorator(); |
| if (status != null) { |
| Set<FileObject> files = Collections.singleton(entry); |
| annotatedName = status.annotateNameHtml(rawName, files); |
| if (rawName.equals(annotatedName)) { |
| annotatedName = null; |
| } |
| if ((annotatedName != null) |
| && (!annotatedName.startsWith("<html>"))) { //NOI18N |
| annotatedName = "<html>" + annotatedName; //NOI18N |
| } |
| if (annotatedName == null) { |
| annotatedName = status.annotateName(rawName, files); |
| } |
| } |
| } catch (FileStateInvalidException ex) { |
| //do nothing and fall through |
| } |
| |
| String name = (annotatedName != null) ? annotatedName : /*XXX escape HTML content*/rawName; |
| return DataEditorSupport.annotateName(name, true, isModified(), !myEntry.getFile().canWrite()); |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * Is modified and is being closed. |
| * @return text to show to the user |
| */ |
| protected String messageSave () { |
| return NbBundle.getMessage ( |
| PropertiesEditorSupport.class, |
| "MSG_SaveFile", // NOI18N |
| getFileLabel() |
| ); |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * Text to use as tooltip for component. |
| * @return text to show to the user |
| */ |
| protected String messageToolTip () { |
| // copied from DataEditorSupport, more or less |
| FileObject fo = myEntry.getFile(); |
| return DataEditorSupport.toolTip(fo, isModified(), !myEntry.getFile().canWrite()); |
| } |
| |
| /** Overrides superclass method. Gets <code>UndoRedo</code> manager which maps |
| * <code>UndoalbleEdit</code>'s to <code>StampFlag</code>'s. */ |
| @Override |
| protected UndoRedo.Manager createUndoRedoManager () { |
| return new UndoRedoStampFlagManager(); |
| } |
| |
| /** |
| * Helper method. Hack on superclass <code>getUndoRedo()</code> method, to widen its protected modifier. |
| * Needs to be accessed from outside this class (in <code>PropertiesOpen</code>). |
| * @see PropertiesOpen |
| */ |
| UndoRedo.Manager getUndoRedoManager() { |
| return super.getUndoRedo(); |
| } |
| |
| /** |
| * Helper method. Used only by <code>PropertiesOpen</code> support when closing last Table component. |
| * Note: It's quite ugly by-pass of <code>notifyClosed()</code> method. Should be revised. |
| */ |
| void forceNotifyClosed() { |
| super.notifyClosed(); |
| } |
| |
| /** Helper method. Saves this entry. */ |
| private void saveThisEntry() throws IOException { |
| FileSystem.AtomicAction aa = new SaveImpl(this); |
| FileUtil.runAtomicAction(aa); |
| // super.saveDocument(); |
| // #32777 - it can happen that save operation was interrupted |
| // and file is still modified. Mark it unmodified only when it is really |
| // not modified. |
| if (!env.isModified()) { |
| myEntry.setModified(false); |
| } |
| } |
| |
| final void superSaveDoc() throws IOException { |
| super.saveDocument(); |
| } |
| |
| /** |
| * Save the document under a new file name and/or extension. |
| * @param folder New folder to save the DataObject to. |
| * @param fileName New file name to save the DataObject to. |
| * @throws java.io.IOException If the operation failed |
| * @since 6.3 |
| */ |
| public void saveAs( FileObject folder, String fileName ) throws IOException { |
| //ask the user for a new file name to save to |
| String newExtension = FileUtil.getExtension( fileName ); |
| |
| DataObject newDob = null; |
| DataObject currentDob = myEntry.getDataObject(); |
| if( !currentDob.isModified() || null == getDocument() ) { |
| //the document is not modified on disk, we copy/rename the file |
| DataFolder df = DataFolder.findFolder( folder ); |
| |
| FileObject newFile = folder.getFileObject(fileName); |
| if( null != newFile ) { |
| //remove the target file if it already exists |
| newFile.delete(); |
| } |
| |
| newFile = myEntry.copyRename(df.getPrimaryFile(), getFileNameNoExtension(fileName), newExtension); |
| if (null != newFile) { |
| newDob = DataObject.find(newFile); |
| } |
| } else { |
| //the document is modified in editor, we need to save the editor kit instead |
| FileObject newFile = FileUtil.createData( folder, fileName ); |
| saveDocumentAs( newFile.getOutputStream() ); |
| currentDob.setModified( false ); |
| newDob = DataObject.find( newFile ); |
| } |
| |
| if( null != newDob ) { |
| OpenCookie c = newDob.getCookie( OpenCookie.class ); |
| if( null != c ) { |
| //close the original document |
| close( false ); |
| //open the new one |
| c.open(); |
| } |
| } |
| } |
| |
| private String getFileNameNoExtension(String fileName) { |
| int index = fileName.lastIndexOf("."); // NOI18N |
| |
| if (index == -1) { |
| return fileName; |
| } else { |
| return fileName.substring(0, index); |
| } |
| } |
| |
| |
| /** |
| * Save the document to a new file. |
| * @param output |
| * @exception IOException on I/O error |
| * @since 6.3 |
| */ |
| private void saveDocumentAs( final OutputStream output ) throws IOException { |
| |
| final StyledDocument myDoc = getDocument(); |
| |
| // save the document as a reader |
| class SaveAsWriter implements Runnable { |
| private IOException ex; |
| |
| public void run() { |
| try { |
| OutputStream os = null; |
| |
| try { |
| os = new BufferedOutputStream( output ); |
| EditorKit kit = createEditorKit(); |
| saveFromKitToStream( myDoc, kit, os ); |
| |
| os.close(); // performs firing |
| os = null; |
| |
| } catch (BadLocationException ex2) { |
| LOG.log(Level.INFO, null, ex2); |
| } finally { |
| if (os != null) { // try to close if not yet done |
| os.close(); |
| } |
| } |
| } catch (IOException e) { |
| this.ex = e; |
| } |
| } |
| |
| public void after() throws IOException { |
| if (ex != null) { |
| throw ex; |
| } |
| } |
| } |
| |
| SaveAsWriter saveAsWriter = new SaveAsWriter(); |
| myDoc.render(saveAsWriter); |
| saveAsWriter.after(); |
| } |
| |
| /** Helper method. |
| * @return whether there is an table view opened */ |
| public synchronized boolean hasOpenedTableComponent() { |
| PropertiesDataObject dataObject = (PropertiesDataObject) myEntry.getDataObject(); |
| if (dataObject.getBundleStructureOrNull() == null || dataObject.getBundleStructure().getEntryCount()==0) { |
| return false; |
| } |
| return dataObject.getOpenSupport().hasOpenedTableComponent(); |
| } |
| |
| /** |
| * Helper method. |
| * @return whether there is an open editor component. */ |
| public synchronized boolean hasOpenedEditorComponent() { |
| Enumeration en = allEditors.getComponents (); |
| return en.hasMoreElements (); |
| } |
| |
| /** Class which exist only due comaptibility with version 3.0. */ |
| private static final class Env extends Environment { |
| /** Generated Serialized Version UID. */ |
| static final long serialVersionUID = -9218186467757330339L; |
| |
| /** Used for deserialization. */ |
| private PropertiesFileEntry entry; |
| |
| /** */ |
| public Env(PropertiesFileEntry entry) { |
| super(entry); |
| } |
| |
| /** Adds passing entry field to superclass. */ |
| private void readObject(java.io.ObjectInputStream in) |
| throws IOException, ClassNotFoundException { |
| in.defaultReadObject(); |
| |
| if (this.entry != null) { |
| super.entry = this.entry; |
| } |
| } |
| } |
| |
| |
| /** Nested class. Implementation of <code>ClonableEditorSupport.Env</code> interface. */ |
| private static class Environment implements CloneableEditorSupport.Env, |
| PropertyChangeListener, SaveCookie { |
| |
| /** generated Serialized Version UID */ |
| static final long serialVersionUID = 354528097109874355L; |
| |
| /** Entry on which is support build. */ |
| private PropertiesFileEntry entry; |
| |
| /** Lock acquired after the first modification and used in <code>save</code> method. */ |
| private transient FileLock fileLock; |
| |
| /** The file object this environment is associated to. |
| * This file object can be changed by a call to refresh file. |
| */ |
| private transient FileObject fileObject; |
| |
| /** Spport for firing of property changes. */ |
| private transient PropertyChangeSupport propSupp; |
| |
| /** Support for firing of vetoable changes. */ |
| private transient VetoableChangeSupport vetoSupp; |
| |
| private transient EnvironmentListener envListener; |
| |
| /** Constructor. |
| * @param obj this support should be associated with |
| */ |
| public Environment (PropertiesFileEntry entry) { |
| LOG.finer("PropertiesEditorSupport(<PropertiesFileEntry>)");//NOI18N |
| LOG.finer(" - new Environment(<PropertiesFileEntry>)"); //NOI18N |
| this.entry = entry; |
| envListener = new EnvironmentListener(this); |
| entry.getFile().addFileChangeListener(envListener); |
| entry.addPropertyChangeListener(this); |
| } |
| /** Getter for the file to work on. |
| * @return the file |
| */ |
| private FileObject getFileImpl () { |
| // updates the file if there was a change |
| changeFile(); |
| return fileObject; |
| } |
| |
| protected final DataObject getDataObject() { |
| return entry.getDataObject(); |
| } |
| /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Adds property listener. */ |
| public void addPropertyChangeListener(PropertyChangeListener l) { |
| LOG.finer("Environment.addPropertyChangeListener(...)"); //NOI18N |
| prop().addPropertyChangeListener (l); |
| } |
| |
| |
| /** Accepts property changes from entry and fires them to own listeners. */ |
| public void propertyChange(PropertyChangeEvent evt) { |
| if (LOG.isLoggable(FINER)) { |
| LOG.finer("Environment.propertyChange(" //NOI18N |
| + evt.getPropertyName() |
| + ", " + evt.getOldValue() //NOI18N |
| + ", " + evt.getNewValue() //NOI18N |
| + ')'); |
| } |
| // We will handle the object invalidation here. |
| if(DataObject.PROP_VALID.equals(evt.getPropertyName ())) { |
| // do not check it if old value is not true |
| if (Boolean.FALSE.equals(evt.getOldValue())) { |
| return; |
| } |
| |
| // loosing validity |
| PropertiesEditorSupport support = (PropertiesEditorSupport)findCloneableOpenSupport(); |
| if(support != null) { |
| |
| // mark the object as not being modified, so nobody |
| // will ask for save |
| unmarkModified(); |
| |
| support.close(false); |
| } |
| } else { |
| firePropertyChange ( |
| evt.getPropertyName(), |
| evt.getOldValue(), |
| evt.getNewValue() |
| ); |
| } |
| } |
| |
| /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Removes property listener. */ |
| public void removePropertyChangeListener(PropertyChangeListener l) { |
| LOG.finer("Environment.removePropertyChangeListener(...)"); //NOI18N |
| prop().removePropertyChangeListener (l); |
| } |
| |
| /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Adds veto listener. */ |
| public void addVetoableChangeListener(VetoableChangeListener l) { |
| LOG.finer("Environment.addVetoableChangeListener(...)"); //NOI18N |
| veto().addVetoableChangeListener (l); |
| } |
| |
| /** Implements <code>CloneableEditorSupport.Env</code> inetrface. Removes veto listener. */ |
| public void removeVetoableChangeListener(VetoableChangeListener l) { |
| LOG.finer("Environment.removeVetoableChangeListener(...)"); //NOI18N |
| veto().removeVetoableChangeListener (l); |
| } |
| |
| /** Overrides superclass method. |
| * Note: in fact it returns <code>CloneableEditorSupport</code> instance. |
| * @return the support or null if the environemnt is not in valid |
| * state and the CloneableOpenSupport cannot be found for associated |
| * entry object |
| */ |
| public CloneableOpenSupport findCloneableOpenSupport() { |
| return (PropertiesEditorSupport)entry.getCookieSet().getCookie(EditCookie.class); |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * Test whether the support is in valid state or not. |
| * It could be invalid after deserialization when the object it |
| * referenced to does not exist anymore. |
| * @return true or false depending on its state |
| */ |
| public boolean isValid() { |
| return entry.getDataObject().isValid(); |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * Test whether the object is modified or not. |
| * @return true if the object is modified |
| */ |
| public boolean isModified() { |
| return entry.isModified(); |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * First of all tries to lock the primary file and |
| * if it succeeds it marks the data object modified. |
| * @exception IOException if the environment cannot be marked modified |
| * (for example when the file is readonly), when such exception |
| * is the support should discard all previous changes |
| */ |
| public void markModified() throws java.io.IOException { |
| LOG.finer("Environment.markModified()"); //NOI18N |
| if (fileLock == null || !fileLock.isValid()) { |
| fileLock = entry.takeLock(); |
| } |
| |
| entry.setModified(true); |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * Reverse method that can be called to make the environment |
| * unmodified. |
| */ |
| public void unmarkModified() { |
| LOG.finer("Environment.unmarkModified()"); //NOI18N |
| if (fileLock != null && fileLock.isValid()) { |
| fileLock.releaseLock(); |
| } |
| |
| entry.setModified(false); |
| } |
| /** |
| * Called from the <code>EnvironmentListener</code>. |
| */ |
| final void updateDocumentProperty () { |
| //Update document TitleProperty |
| EditorCookie ec = getDataObject().getCookie(EditorCookie.class); |
| if (ec != null) { |
| StyledDocument doc = ec.getDocument(); |
| if (doc != null) { |
| doc.putProperty(Document.TitleProperty, |
| FileUtil.getFileDisplayName(getDataObject().getPrimaryFile())); |
| } |
| } |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * Mime type of the document. |
| * @return the mime type to use for the document |
| */ |
| public String getMimeType() { |
| return getFileImpl().getMIMEType(); |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * The time when the data has been modified. */ |
| public Date getTime() { |
| // #32777 - refresh file object and return always the actual time |
| getFileImpl().refresh(); |
| return getFileImpl().lastModified(); |
| } |
| |
| /** Method that allows subclasses to notify this environment that |
| * the file associated with this support has changed and that |
| * the environment should listen on modifications of different |
| * file object. |
| */ |
| protected final void changeFile () { |
| FileObject newFile = entry.getFile (); |
| if (newFile.equals (fileObject)) { |
| // the file has not been updated |
| return; |
| } |
| |
| boolean lockAgain; |
| if (fileLock != null) { |
| // <> NB #61818 In case the lock was not active (isValid() == false), the new lock was taken, |
| // which seems to be incorrect. There is taken a lock on new file, while it there wasn't on the old one. |
| // fileLock.releaseLock (); |
| // lockAgain = true; |
| // ===== |
| if(fileLock.isValid()) { |
| LOG.fine("changeFile releaseLock: " + fileLock + " for " + fileObject); // NOI18N |
| fileLock.releaseLock (); |
| lockAgain = true; |
| } else { |
| fileLock = null; |
| lockAgain = false; |
| } |
| // </> |
| } else { |
| lockAgain = false; |
| } |
| |
| boolean wasNull = fileObject == null; |
| |
| fileObject = newFile; |
| LOG.fine("changeFile: " + newFile + " for " + fileObject); // NOI18N |
| if (envListener != null) |
| fileObject.removeFileChangeListener(envListener); |
| envListener = new EnvironmentListener(this); |
| fileObject.addFileChangeListener (envListener); |
| |
| if (lockAgain) { // refresh lock |
| try { |
| fileLock = entry.takeLock (); |
| LOG.fine("changeFile takeLock: " + fileLock + " for " + fileObject); // NOI18N |
| } catch (IOException e) { |
| Logger.getLogger(PropertiesEditorSupport.class.getName()).log(Level.WARNING, null, e); |
| } |
| } |
| if (!wasNull) { |
| firePropertyChange("expectedTime", null, getTime()); // NOI18N |
| } |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * Obtains the input stream. |
| * @exception IOException if an I/O error occures |
| */ |
| public InputStream inputStream() throws IOException { |
| LOG.finer("Environment.inputStream()"); //NOI18N |
| return getFileImpl().getInputStream(); |
| } |
| |
| /** |
| * Implements <code>CloneableEditorSupport.Env</code> interface. |
| * Obtains the output stream. |
| * @exception IOException if an I/O error occures |
| */ |
| public OutputStream outputStream() throws IOException { |
| LOG.finer("Environment.outputStream()"); //NOI18N |
| if (fileLock == null || !fileLock.isValid()) { |
| fileLock = entry.takeLock (); |
| } |
| LOG.fine("outputStream after takeLock: " + fileLock + " for " + fileObject); // NOI18N |
| try { |
| return getFileImpl ().getOutputStream (fileLock); |
| } catch (IOException fse) { |
| // [pnejedly] just retry once. |
| // Ugly workaround for #40552 |
| if (fileLock == null || !fileLock.isValid()) { |
| fileLock = entry.takeLock (); |
| } |
| LOG.fine("ugly workaround for #40552: " + fileLock + " for " + fileObject); // NOI18N |
| return getFileImpl ().getOutputStream (fileLock); |
| } |
| // return entry.getFile().getOutputStream(fileLock); |
| } |
| |
| /** |
| * Implements <code>SaveCookie</code> interface. |
| * Invoke the save operation. |
| * @throws IOException if the object could not be saved |
| */ |
| public void save() throws IOException { |
| LOG.finer("Environment.save()"); //NOI18N |
| // Do saving job. Note it gets editor support, not open support. |
| ((PropertiesEditorSupport)findCloneableOpenSupport()).saveThisEntry(); |
| } |
| |
| /** Fires property change. |
| * @param name the name of property that changed |
| * @param oldValue old value |
| * @param newValue new value |
| */ |
| private void firePropertyChange (String name, Object oldValue, Object newValue) { |
| prop().firePropertyChange (name, oldValue, newValue); |
| } |
| |
| /** Fires vetoable change. |
| * @param name the name of property that changed |
| * @param oldValue old value |
| * @param newValue new value |
| */ |
| private void fireVetoableChange (String name, Object oldValue, Object newValue) throws PropertyVetoException { |
| veto ().fireVetoableChange (name, oldValue, newValue); |
| } |
| |
| /** Lazy getter for property change support. */ |
| private PropertyChangeSupport prop() { |
| synchronized (this) { |
| if (propSupp == null) { |
| propSupp = new PropertyChangeSupport (this); |
| } |
| } |
| |
| return propSupp; |
| } |
| |
| /** Lazy getter for vetoable support. */ |
| private VetoableChangeSupport veto() { |
| synchronized (this) { |
| if (vetoSupp == null) { |
| vetoSupp = new VetoableChangeSupport (this); |
| } |
| } |
| return vetoSupp; |
| } |
| |
| /** Helper method. Adds save cookie to the entry. */ |
| private void addSaveCookie() { |
| LOG.finer("Environment.addSaveCookie(...)"); //NOI18N |
| if (entry.getCookie(SaveCookie.class) == null) { |
| entry.getCookieSet().add(this); |
| } |
| //Need to add cookie to DataObject since saveAll use it, and |
| //OpenCookie may not be initialized |
| PropertiesDataObject dataObject = (PropertiesDataObject) getDataObject(); |
| dataObject.updateModificationStatus(); |
| if (dataObject.getCookie(SaveCookie.class) == null){ |
| dataObject.getCookieSet0().add(this); |
| } |
| } |
| |
| /** Helper method. Removes save cookie from the entry. */ |
| private void removeSaveCookie() { |
| LOG.finer("Environment.removeSaveCookie(...)"); //NOI18N |
| // remove Save cookie from the entry |
| SaveCookie sc = entry.getCookie(SaveCookie.class); |
| |
| if (sc != null && sc.equals(this)) { |
| entry.getCookieSet().remove(this); |
| } |
| final SaveCookie cookie = this; |
| PropertiesRequestProcessor.getInstance().post(new Runnable() { |
| public void run() { |
| PropertiesDataObject dataObject = (PropertiesDataObject) getDataObject(); |
| dataObject.updateModificationStatus(); |
| dataObject.getCookieSet0().remove(cookie); |
| } |
| }); |
| } |
| |
| /** Called from the <code>EnvironmnetListener</code> |
| * @param expected is the change expected |
| * @param time of the change |
| */ |
| private void fileChanged(boolean expected, long time) { |
| LOG.finer("Environment.fileChanged(...)"); //NOI18N |
| if (expected) { |
| // newValue = null means do not ask user whether to reload |
| firePropertyChange (PROP_TIME, null, null); |
| } else { |
| firePropertyChange (PROP_TIME, null, new Date (time)); |
| } |
| } |
| |
| /** Called from the <code>EnvironmentListener</code>. |
| */ |
| final void fileRenamed () { |
| //#151787: Sync timestamp when svn client changes timestamp externally during rename. |
| firePropertyChange("expectedTime", null, getTime()); // NOI18N |
| } |
| |
| /** Called from the <code>EnvironmentListener</code>. |
| * The components are going to be closed anyway and in case of |
| * modified document its asked before if to save the change. */ |
| private void fileRemoved() { |
| LOG.finer("Environment.fileRemoved() ... "); //NOI18N |
| try { |
| fireVetoableChange(PROP_VALID, Boolean.TRUE, Boolean.FALSE); |
| } catch(PropertyVetoException pve) { |
| // Ignore it and close anyway. File doesn't exist anymore. |
| } |
| |
| firePropertyChange(PROP_VALID, Boolean.TRUE, Boolean.FALSE); |
| } |
| } // End of nested class Environment. |
| |
| |
| |
| /** Weak listener on file object that notifies the <code>Environment</code> object |
| * that a file has been modified. */ |
| private static final class EnvironmentListener extends FileChangeAdapter { |
| |
| /** Reference of <code>Environment</code> */ |
| private Reference<Environment> reference; |
| |
| /** @param environment <code>Environment<code> to use |
| */ |
| public EnvironmentListener(Environment environment) { |
| LOG.finer("new EnvironmentListener(<Environment>)"); //NOI18N |
| reference = new WeakReference<Environment>(environment); |
| } |
| |
| @Override |
| public void fileDeleted(FileEvent fe) { |
| Environment myEnv = this.reference.get(); |
| if (myEnv != null) { |
| myEnv.updateDocumentProperty(); |
| myEnv.fileRemoved(); |
| } |
| // super.fileDeleted(fe); |
| } |
| |
| /** Fired when a file is changed. |
| * @param fe the event describing context where action has taken place |
| */ |
| @Override |
| public void fileChanged(FileEvent evt) { |
| if (LOG.isLoggable(FINER)) { |
| LOG.finer("EnviromentListener.fileChanged(...)"); //NOI18N |
| LOG.finer(" - original file: " //NOI18N |
| + FileUtil.getFileDisplayName(evt.getFile())); |
| LOG.finer(" - current file: " //NOI18N |
| + FileUtil.getFileDisplayName((FileObject) evt.getSource())); |
| } |
| //see #160338 |
| if (evt.firedFrom(SaveImpl.DEFAULT)) { |
| return; |
| } |
| Environment environment = reference.get(); |
| if (environment != null) { |
| if(!environment.getFileImpl().equals(evt.getFile()) ) { |
| // If the FileObject was changed. |
| // Remove old listener from old FileObject. |
| evt.getFile().removeFileChangeListener(this); |
| // Add new listener to new FileObject. |
| environment.getFileImpl().addFileChangeListener(new EnvironmentListener(environment)); |
| return; |
| } |
| |
| // #16403. See DataEditorSupport.EnvListener. |
| if(evt.getFile().isVirtual()) { |
| environment.entry.getFile().removeFileChangeListener(this); |
| // File doesn't exist on disk -> simulate env is invalid, |
| // even the fileObject could be valid, see VCS FS. |
| environment.fileRemoved(); |
| environment.entry.getFile().addFileChangeListener(this); |
| } else { |
| environment.fileChanged(evt.isExpected(), evt.getTime()); |
| } |
| } |
| } |
| |
| @Override |
| public void fileRenamed(FileRenameEvent fe) { |
| Environment myEnv = this.reference.get(); |
| if (myEnv != null) { |
| myEnv.updateDocumentProperty(); |
| myEnv.fileRenamed(); |
| } |
| } |
| } // End of nested class EnvironmentListener. |
| |
| /** Inner class for opening editor view at a given key. */ |
| public class PropertiesEditAt implements EditCookie { |
| |
| /** Key at which should be pane opened. (Cursor will be at the position of that key). */ |
| private String key; |
| |
| |
| /** Constructor. */ |
| PropertiesEditAt(String key) { |
| this.key = key; |
| } |
| |
| |
| /** Setter for <code>key</code>. */ |
| public void setKey(String key) { |
| this.key = key; |
| } |
| |
| /** Implementation of <code>EditCookie</code> interface. */ |
| public void edit() { |
| CloneableTopComponent ctc = PropertiesEditorSupport.super.openCloneableTopComponent(); |
| ctc.requestActive(); |
| |
| PropertiesEditor editor = ctc.getLookup().lookup(PropertiesEditor.class); |
| |
| Element.ItemElem item = myEntry.getHandler().getStructure().getItem(key); |
| if (item != null) { |
| int offset = item.getKeyElem().getBounds().getBegin().getOffset(); |
| if ((editor.getPane() != null) && (editor.getPane().getCaret() != null)) { |
| editor.getPane().getCaret().setDot(offset); |
| } |
| } |
| } |
| } // End of inner class PropertiesEditAt. |
| |
| |
| /** Cloneable top component to hold the editor kit. */ |
| @Messages("CTL_SourceTabCaption=&Source") |
| @MultiViewElement.Registration( |
| displayName="#CTL_SourceTabCaption", |
| iconBase="org/netbeans/modules/properties/propertiesObject.png", |
| persistenceType=TopComponent.PERSISTENCE_ONLY_OPENED, |
| preferredID="properties.source", |
| mimeType=PropertiesDataLoader.PROPERTIES_MIME_TYPE, |
| position=1 |
| ) |
| public static class PropertiesEditor extends CloneableEditor implements MultiViewElement { |
| |
| /** Holds the file being edited. */ |
| protected transient PropertiesFileEntry entry; |
| |
| /** Listener for entry's save cookie changes. */ |
| private transient PropertyChangeListener saveCookieLNode; |
| |
| /** Generated serial version UID. */ |
| static final long serialVersionUID =-2702087884943509637L; |
| private MultiViewElementCallback callback; |
| private transient JToolBar bar; |
| |
| private transient PropertiesEditorLookup peLookup; |
| private transient Lookup originalLookup; |
| |
| /** Constructor for deserialization */ |
| public PropertiesEditor() { |
| super(); |
| } |
| |
| /** Creates new editor */ |
| public PropertiesEditor(Lookup lookup) { |
| super(lookup.lookup(PropertiesEditorSupport.class)); |
| PropertiesEditorSupport support = lookup.lookup(PropertiesEditorSupport.class); |
| setActivatedNodes(new Node[] {support.getDataObject().getNodeDelegate()}); |
| } |
| |
| /** Initializes object, used in construction and deserialization. */ |
| private void initialize(PropertiesFileEntry entry) { |
| this.entry = entry; |
| |
| Node n = entry.getNodeDelegate (); |
| setActivatedNodes (new Node[] { n }); |
| |
| updateName(); |
| |
| // entry to the set of listeners |
| saveCookieLNode = new PropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent evt) { |
| if (Node.PROP_COOKIE.equals(evt.getPropertyName()) || |
| DataObject.PROP_NAME.equals(evt.getPropertyName())) |
| { |
| updateName(); |
| } |
| } |
| }; |
| this.entry.addPropertyChangeListener(WeakListeners.propertyChange(saveCookieLNode, this.entry)); |
| } |
| |
| /** |
| * Overrides superclass method. |
| * When closing last view, also close the document. |
| * @return <code>true</code> if close succeeded |
| */ |
| @Override |
| protected boolean closeLast () { |
| return super.closeLast(false); |
| } |
| |
| /** Overrides superclass method. Gets <code>Icon</code>. */ |
| @Override |
| public Image getIcon () { |
| PropertiesDataObject propDO = (PropertiesDataObject) getDataObject(); |
| return ImageUtilities.loadImage( |
| propDO.isMultiLocale() |
| ? "org/netbeans/modules/properties/propertiesLocale.gif" // NOI18N |
| : "org/netbeans/modules/properties/propertiesObject.png"); // NOI18N |
| } |
| |
| /** Overrides superclass method. Gets help context. */ |
| @Override |
| public HelpCtx getHelpCtx() { |
| return new HelpCtx(Util.HELP_ID_EDITLOCALE); |
| } |
| |
| /** Getter for pane. */ |
| private JEditorPane getPane() { |
| return pane; |
| } |
| |
| @Override |
| public Lookup getLookup() { |
| Lookup currentLookup = super.getLookup(); |
| if (currentLookup != originalLookup || null == peLookup) { |
| originalLookup = currentLookup; |
| if(peLookup == null) { |
| peLookup = new PropertiesEditorLookup(Lookups.singleton(PropertiesEditor.this)); |
| } |
| peLookup.updateLookups(originalLookup); |
| } |
| return peLookup; |
| } |
| |
| @Override |
| public JComponent getVisualRepresentation() { |
| return this; |
| } |
| |
| @Override |
| public void componentDeactivated() { |
| super.componentDeactivated(); |
| } |
| |
| @Override |
| public void componentClosed() { |
| super.componentClosed(); |
| } |
| |
| @Override |
| public JComponent getToolbarRepresentation() { |
| JToolBar toolBar = bar; |
| if (toolBar == null) { |
| JEditorPane lPane = getEditorPane(); |
| if (lPane != null) { |
| Document doc = lPane.getDocument(); |
| if (doc instanceof NbDocument.CustomToolbar) { |
| toolBar = ((NbDocument.CustomToolbar)doc).createToolbar(lPane); |
| } |
| } |
| if (toolBar == null) { |
| toolBar = new JToolBar(); |
| } |
| bar = toolBar; |
| } |
| return toolBar; |
| } |
| |
| @Override |
| public void setMultiViewCallback(MultiViewElementCallback callback) { |
| this.callback = callback; |
| PropertiesEditorSupport editor = (PropertiesEditorSupport) cloneableEditorSupport(); |
| editor.attachStatusListener(); |
| } |
| |
| @Messages({ |
| "MSG_SaveModified=File {0} is modified. Save?" |
| }) |
| @Override |
| public CloseOperationState canCloseElement() { |
| final CloneableEditorSupport sup = getLookup().lookup(CloneableEditorSupport.class); |
| Enumeration en = getReference().getComponents(); |
| if (en.hasMoreElements()) { |
| en.nextElement(); |
| if (en.hasMoreElements()) { |
| // at least two is OK |
| return CloseOperationState.STATE_OK; |
| } |
| } |
| |
| PropertiesDataObject dataObject = getDataObject(); |
| if (dataObject.isModified()) { |
| AbstractAction save = new AbstractAction() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| try { |
| sup.saveDocument(); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| }; |
| save.putValue(Action.LONG_DESCRIPTION, Bundle.MSG_SaveModified(FileUtil.getFileDisplayName(dataObject.getPrimaryFile()))); |
| return MultiViewFactory.createUnsafeCloseState("editor", save, null); |
| } |
| return CloseOperationState.STATE_OK; |
| } |
| |
| @Override |
| public void componentActivated() { |
| super.componentActivated(); |
| } |
| |
| @Override |
| public void componentHidden() { |
| super.componentHidden(); |
| } |
| |
| @Override |
| public void componentOpened() { |
| super.componentOpened(); |
| } |
| |
| @Override |
| public void componentShowing() { |
| if (callback != null) { |
| updateName(); |
| } |
| super.componentShowing(); |
| } |
| |
| @Override |
| public void requestVisible() { |
| if (callback != null) { |
| callback.requestVisible(); |
| } else { |
| super.requestVisible(); |
| } |
| } |
| |
| @Override |
| public void requestActive() { |
| if (callback != null) { |
| callback.requestActive(); |
| } else { |
| super.requestActive(); |
| } |
| } |
| |
| @Override |
| public void updateName() { |
| super.updateName(); |
| Mutex.EVENT.writeAccess( |
| new Runnable() { |
| @Override |
| public void run() { |
| if (callback != null) { |
| TopComponent tc = callback.getTopComponent(); |
| tc.setHtmlDisplayName(getHtmlDisplayName()); |
| tc.setDisplayName(getDisplayName()); |
| tc.setName(getName()); |
| tc.setToolTipText(getToolTipText()); |
| } |
| } |
| } |
| ); |
| } |
| |
| @Override |
| public void open() { |
| if (callback != null) { |
| callback.requestVisible(); |
| } else { |
| super.open(); |
| } |
| } |
| |
| private PropertiesDataObject getDataObject() { |
| return (PropertiesDataObject) ((PropertiesEditorSupport) cloneableEditorSupport()).getDataObject(); |
| } |
| |
| } // End of nested class PropertiesEditor. |
| |
| |
| /** Inner class. UndoRedo manager which saves a StampFlag |
| * for each UndoAbleEdit. |
| */ |
| class UndoRedoStampFlagManager extends UndoRedo.Manager { |
| |
| /** Hash map of weak reference keys (UndoableEdit's) to their StampFlag's. */ |
| WeakHashMap<UndoableEdit,StampFlag> stampFlags |
| = new WeakHashMap<UndoableEdit,StampFlag>(5); |
| |
| /** Overrides superclass method. Adds StampFlag to UndoableEdit. */ |
| @Override |
| public synchronized boolean addEdit(UndoableEdit anEdit) { |
| stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), |
| // ((PropertiesDataObject)PropertiesEditorSupport.this.myEntry.getDataObject()).getOpenSupport().atomicUndoRedoFlag )); |
| PropertiesEditorSupport.this.myEntry.atomicUndoRedoFlag )); |
| return super.addEdit(anEdit); |
| } |
| |
| /** Overrides superclass method. Adds StampFlag to UndoableEdit. */ |
| @Override |
| public boolean replaceEdit(UndoableEdit anEdit) { |
| stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), |
| // ((PropertiesDataObject)PropertiesEditorSupport.this.myEntry.getDataObject()).getOpenSupport().atomicUndoRedoFlag )); |
| PropertiesEditorSupport.this.myEntry.atomicUndoRedoFlag )); |
| return super.replaceEdit(anEdit); |
| } |
| |
| /** Overrides superclass method. Updates time stamp for the edit. */ |
| @Override |
| public synchronized void undo() throws CannotUndoException { |
| UndoableEdit anEdit = editToBeUndone(); |
| if(anEdit != null) { |
| Object atomicFlag = stampFlags.get(anEdit).getAtomicFlag(); // atomic flag remains |
| super.undo(); |
| stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), atomicFlag)); |
| } |
| } |
| |
| /** Overrides superclass method. Updates time stamp for that edit. */ |
| @Override |
| public synchronized void redo() throws CannotRedoException { |
| UndoableEdit anEdit = editToBeRedone(); |
| if(anEdit != null) { |
| Object atomicFlag = stampFlags.get(anEdit).getAtomicFlag(); // atomic flag remains |
| super.redo(); |
| stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), atomicFlag)); |
| } |
| } |
| |
| /** Method which gets time stamp of next Undoable edit to be undone. |
| * @ return time stamp in milliseconds or 0 (if don't exit edit to be undone). */ |
| public long getTimeStampOfEditToBeUndone() { |
| UndoableEdit nextUndo = editToBeUndone(); |
| if (nextUndo == null) { |
| return 0L; |
| } else { |
| return stampFlags.get(nextUndo).getTimeStamp(); |
| } |
| } |
| |
| /** Method which gets time stamp of next Undoable edit to be redone. |
| * @ return time stamp in milliseconds or 0 (if don't exit edit to be redone). */ |
| public long getTimeStampOfEditToBeRedone() { |
| UndoableEdit nextRedo = editToBeRedone(); |
| if (nextRedo == null) { |
| return 0L; |
| } else { |
| return stampFlags.get(nextRedo).getTimeStamp(); |
| } |
| } |
| |
| /** Method which gets atomic flag of next Undoable edit to be undone. |
| * @ return atomic flag in milliseconds or 0 (if don't exit edit to be undone). */ |
| public Object getAtomicFlagOfEditToBeUndone() { |
| UndoableEdit nextUndo = editToBeUndone(); |
| if (nextUndo == null) { |
| return null; |
| } else { |
| return (stampFlags.get(nextUndo)).getAtomicFlag(); |
| } |
| } |
| |
| /** Method which gets atomic flag of next Undoable edit to be redone. |
| * @ return time stamp in milliseconds or 0 (if don't exit edit to be redone). */ |
| public Object getAtomicFlagOfEditToBeRedone() { |
| UndoableEdit nextRedo = editToBeRedone(); |
| if (nextRedo == null) { |
| return null; |
| } else { |
| return (stampFlags.get(nextRedo)).getAtomicFlag(); |
| } |
| } |
| |
| } // End of inner class UndoRedoTimeStampManager. |
| |
| /** Simple nested class for storing time stamp and atomic flag used |
| * in <code>UndoRedoStampFlagManager</code>. |
| */ |
| static class StampFlag { |
| |
| /** Time stamp when was an UndoableEdit (to which is this class mapped via |
| * UndoRedoStampFlagManager,) was created, replaced, undone, or redone. */ |
| private long timeStamp; |
| |
| /** Atomic flag. If this object is not null it means that an UndoableEdit ( to which |
| * is this class mapped via UndoRedoStampFlagManager,) was created as part of one |
| * action which could consist from more UndoableEdits in differrent editor supports. |
| * These Undoable edits are marked with this (i.e. same) object. */ |
| private Object atomicFlag; |
| |
| /** Consructor. */ |
| public StampFlag(long timeStamp, Object atomicFlag) { |
| this.timeStamp = timeStamp; |
| this.atomicFlag = atomicFlag; |
| } |
| |
| /** Getter for time stamp. */ |
| public long getTimeStamp() { |
| return timeStamp; |
| } |
| |
| /** Setter for time stamp. */ |
| public void setTimeStamp(long timeStamp) { |
| this.timeStamp = timeStamp; |
| } |
| |
| /** Getter for atomic flag. |
| @ return Returns null if is not linked with more Undoable edits.*/ |
| public Object getAtomicFlag() { |
| return atomicFlag; |
| } |
| } // End of nested class TimeStamp. |
| |
| private static class SaveImpl implements AtomicAction { |
| private static final SaveImpl DEFAULT = new SaveImpl(null); |
| private final PropertiesEditorSupport des; |
| |
| public SaveImpl(PropertiesEditorSupport des) { |
| this.des = des; |
| } |
| |
| public void run() throws IOException { |
| if (des.desEnv().isModified() && des.isEnvReadOnly()) { |
| IOException e = new IOException("File is read-only: " + ((Environment) des.env).getFileImpl()); // NOI18N |
| // UIException.annotateUser(e, null, org.openide.util.NbBundle.getMessage(org.openide.loaders.DataObject.class, "MSG_FileReadOnlySaving", new java.lang.Object[]{((org.netbeans.modules.properties.PropertiesEditorSupport.Environment) des.env).getFileImpl().getNameExt()}), null, null); |
| throw e; |
| } |
| des.superSaveDoc(); |
| } |
| |
| @Override |
| public int hashCode() { |
| return getClass().hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj != null && getClass() == obj.getClass(); |
| } |
| } |
| |
| private static final class PropertiesEditorLookup extends ProxyLookup { |
| private Lookup initialLookup; |
| public PropertiesEditorLookup(Lookup lookup) { |
| super(lookup); |
| this.initialLookup = lookup; |
| } |
| |
| public void updateLookups(Lookup additionalLookup) { |
| setLookups(new Lookup[] {initialLookup, additionalLookup}); |
| } |
| } |
| |
| } |