alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.text;

import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.util.*;
import java.lang.ref.Reference;

import javax.swing.text.*;
import org.openide.DialogDisplayer;

import org.openide.actions.*;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.*;
import org.openide.nodes.Node;
import org.openide.nodes.NodeAdapter;
import org.openide.nodes.NodeListener;
import org.openide.loaders.*;
import org.openide.util.Mutex;
import org.openide.windows.*;
import org.openide.util.NbBundle;
import org.openide.util.Lookup;
import org.openide.util.lookup.*;

/** Support for associating an editor and a Swing {@link Document} to a data object.
* 
*
* @author Jaroslav Tulach
*/
public class DataEditorSupport extends CloneableEditorSupport {
    /** Which data object we are associated with */
    private final DataObject obj;
    /** listener to asociated node's events */
    private NodeListener nodeL;
    
    /** Editor support for a given data object. The file is taken from the
    * data object and is updated if the object moves or renames itself.
    * @param obj object to work with
    * @param env environment to pass to 
    */
    public DataEditorSupport (DataObject obj, CloneableEditorSupport.Env env) {
        super (env, createLookup(obj));
        this.obj = obj;
    }
    
    /** Getter of the data object that this support is associated with.
    * @return data object passed in constructor
    */
    public final DataObject getDataObject () {
        return obj;
    }

    /** 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 (DataObject.class , "CTL_ObjectOpen", // NOI18N
            obj.getPrimaryFile().getNameExt(),
            FileUtil.getFileDisplayName(obj.getPrimaryFile())
        );
    }
    

    /** 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 (DataObject.class, "CTL_ObjectOpened", // NOI18N
            obj.getPrimaryFile().getNameExt(),
            FileUtil.getFileDisplayName(obj.getPrimaryFile())
        );
    }

    /** Constructs message that should be displayed when the data object
    * is modified and is being closed.
    *
    * @return text to show to the user
    */
    protected String messageSave () {
        return NbBundle.getMessage (
            DataObject.class,
            "MSG_SaveFile", // NOI18N
            obj.getPrimaryFile().getNameExt()
        );
    }
    
    /** Constructs message that should be used to name the editor component.
    *
    * @return name of the editor
    */
    protected String messageName () {
        if (! obj.isValid()) return ""; // NOI18N

        String name;
        if(DataNode.getShowFileExtensions()) {
            name = obj.getPrimaryFile().getNameExt();
        } else {
            name = obj.getPrimaryFile().getName();
        }
        return addFlagsToName(name);
    }
        
    /** Helper only. */
    private String addFlagsToName(String name) {
        int version = 3;
        if (isModified ()) {
            if (obj.getPrimaryFile ().isReadOnly ()) {
                version = 2;
            } else {
                version = 1;
            }
        } else
        if (obj.getPrimaryFile ().isReadOnly ()) {
            version = 0;
        }

        return NbBundle.getMessage (DataObject.class, "LAB_EditorName",
		new Integer (version), name );
    }
    
    protected String documentID() {
        if (! obj.isValid()) return ""; // NOI18N
        return obj.getPrimaryFile().getName();
    }

    /** Text to use as tooltip for component.
    *
    * @return text to show to the user
    */
    protected String messageToolTip () {
        // update tooltip
        return FileUtil.getFileDisplayName(obj.getPrimaryFile());
    }
    
    /** Computes display name for a line based on the 
     * name of the associated DataObject and the line number.
     *
     * @param line the line object to compute display name for
     * @return display name for the line like "MyFile.java:243"
     *
     * @since 4.3
     */
    protected String messageLine (Line line) {
        return NbBundle.getMessage(DataObject.class, "FMT_LineDisplayName2",
            obj.getPrimaryFile().getNameExt(),
            FileUtil.getFileDisplayName(obj.getPrimaryFile()),
            new Integer(line.getLineNumber() + 1));
    }
    
    
    /** Annotates the editor with icon from the data object and also sets 
     * appropriate selected node. But only in the case the data object is valid.
     * This implementation also listen to display name and icon chamges of the
     * node and keeps editor top component up-to-date. If you override this
     * method and not call super, please note that you will have to keep things
     * synchronized yourself. 
     *
     * @param editor the editor that has been created and should be annotated
     */
    protected void initializeCloneableEditor (CloneableEditor editor) {
        // Prevention to bug similar to #17134. Don't call getNodeDelegate
        // on invalid data object. Top component should be discarded later.
        if(obj.isValid()) {
            Node ourNode = obj.getNodeDelegate();
            editor.setActivatedNodes (new Node[] { ourNode });
            editor.setIcon(ourNode.getIcon (java.beans.BeanInfo.ICON_COLOR_16x16));
            NodeListener nl = new DataNodeListener(editor);
            ourNode.addNodeListener(org.openide.nodes.NodeOp.weakNodeListener (nl, ourNode));
            nodeL = nl;
        }
    }

    /** Called when closed all components. Overrides superclass method,
     * also unregisters listening on node delegate. */
    protected void notifyClosed() {
        // #27645 All components were closed, unregister weak listener on node.
        nodeL = null;
        
        super.notifyClosed();
    }
    
    /** 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
    */
    protected StyledDocument createStyledDocument (EditorKit kit) {
        StyledDocument doc = super.createStyledDocument (kit);
            
        // set document name property
        doc.putProperty(javax.swing.text.Document.TitleProperty,
            FileUtil.getFileDisplayName(obj.getPrimaryFile())
        );
        // set dataobject to stream desc property
        doc.putProperty(javax.swing.text.Document.StreamDescriptionProperty,
            obj
        );
        
        return doc;
    }

    /** Checks whether is possible to close support components.
     * Overrides superclass method, adds checking
     * for read-only property of saving file and warns user in that case. */
    protected boolean canClose() {
        if(env().isModified() && isEnvReadOnly()) {
            Object result = DialogDisplayer.getDefault().notify(
                new NotifyDescriptor.Confirmation(
                    NbBundle.getMessage(DataObject.class,
                        "MSG_FileReadOnlyClosing", 
                        new Object[] {((Env)env).getFileImpl().getNameExt()}),
                    NotifyDescriptor.OK_CANCEL_OPTION,
                    NotifyDescriptor.WARNING_MESSAGE
            ));

            return result == NotifyDescriptor.OK_OPTION;
        }
        
        return super.canClose();
    }

    /** Saves document. Overrides superclass method, adds checking
     * for read-only property of saving file and warns user in that case. */
    public void saveDocument() throws IOException {
        if(env().isModified() && isEnvReadOnly()) {
            DialogDisplayer.getDefault().notify(
                new NotifyDescriptor.Message(
                    NbBundle.getMessage(DataObject.class,
                        "MSG_FileReadOnlySaving", 
                        new Object[] {((Env)env).getFileImpl().getNameExt()}),
                NotifyDescriptor.WARNING_MESSAGE
            ));
            return;
        }
        super.saveDocument();
    }

    /** Indicates whether the Env is read only. */
    boolean isEnvReadOnly() {
        CloneableEditorSupport.Env env = env();
        return env instanceof Env && ((Env)env).getFileImpl().isReadOnly();
    }
    
    /** Needed for EditorSupport */
    final DataObject getDataObjectHack2 () {
        return obj;
    }
    
    /** Support method that extracts a DataObject from a Line. If the 
     * line is created by a DataEditorSupport then associated DataObject
     * can be accessed by this method.
     *
     * @param l line object 
     * @return data object or null
     *
     * @since 4.3
     */
    public static DataObject findDataObject (Line l) {
        if (l == null) throw new NullPointerException();
        return (DataObject)l.getLookup ().lookup (DataObject.class);
    }
    
    /** Environment that connects the data object and the CloneableEditorSupport.
    */
    public static abstract class Env extends OpenSupport.Env 
    implements CloneableEditorSupport.Env, java.io.Serializable,
    PropertyChangeListener, VetoableChangeListener {
        /** generated Serialized Version UID */
        static final long serialVersionUID = -2945098431098324441L;

        /** The file object this environment is associated to.
        * This file object can be changed by a call to refresh file.
        */
        private transient FileObject fileObject;

        /** Lock acquired after the first modification and used in save.
        * Transient => is not serialized.
        */
        private transient FileLock fileLock;
        /** did we warned about the size of the file?
         */
        private transient boolean warned;
        
        /** Constructor.
        * @param obj this support should be associated with
        */
        public Env (DataObject obj) {
            super (obj);
        }
        
        /** Getter for the file to work on.
        * @return the file
        */
        private FileObject getFileImpl () {
            // updates the file if there was a change
	    changeFile();
            return fileObject;
        }
        
        /** Getter for file associated with this environment.
        * @return the file input/output operation should be performed on
        */
        protected abstract FileObject getFile ();

        /** Locks the file.
        * @return the lock on the file getFile ()
        * @exception IOException if the file cannot be locked
        */
        protected abstract FileLock takeLock () throws IOException;
                
        /** 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 = getFile ();
            
            if (newFile.equals (fileObject)) {
                // the file has not been updated
                return;
            }
            
            boolean lockAgain;
            if (fileLock != null) {
                fileLock.releaseLock ();
                lockAgain = true;
            } else {
                lockAgain = false;
            }

            fileObject = newFile;
            fileObject.addFileChangeListener (new EnvListener (this));

            if (lockAgain) { // refresh lock
                try {
                    fileLock = takeLock ();
                } catch (IOException e) {
                    ErrorManager.getDefault ().notify (
                	    ErrorManager.INFORMATIONAL, e);
                }
            }
            
        }
        
        
        /** Obtains the input stream.
        * @exception IOException if an I/O error occures
        */
        public InputStream inputStream() throws IOException {
            final FileObject fo = getFileImpl ();
            if (!warned && fo.getSize () > 1024 * 1024) {
                class ME extends org.openide.util.UserQuestionException {
                    private long size;
                    
                    public ME (long size) {
                        super ("The file is too big. " + size + " bytes.");
                        this.size = size;
                    }
                    
                    public String getLocalizedMessage () {
                        Object[] arr = {
                            fo.getPath (),
                            fo.getNameExt (),
                            new Long (size), // bytes
                            new Long (size / 1024 + 1), // kilobytes
                            new Long (size / (1024 * 1024)), // megabytes
                            new Long (size / (1024 * 1024 * 1024)), // gigabytes
                        };
                        return NbBundle.getMessage(DataObject.class, "MSG_ObjectIsTooBig", arr);
                    }
                    
                    public void confirmed () {
                        warned = true;
                    }
                }
                throw new ME (fo.getSize ());
            }
            InputStream is = getFileImpl ().getInputStream ();
            return is;
        }
        
        /** Obtains the output stream.
        * @exception IOException if an I/O error occures
        */
        public OutputStream outputStream() throws IOException {
            if (fileLock == null || !fileLock.isValid()) {
                fileLock = takeLock ();
            }
            try {
                return getFileImpl ().getOutputStream (fileLock);
            } catch (IOException fse) {
	        // [pnejedly] just retry once.
		// Ugly workaround for #40552
                if (fileLock == null || !fileLock.isValid()) {
                    fileLock = takeLock ();
                }
                return getFileImpl ().getOutputStream (fileLock);
            }	    
        }
        
        /** The time when the data has been modified
        */
        public Date getTime() {
            // #32777 - refresh file object and return always the actual time
            getFileImpl().refresh(false);
            return getFileImpl ().lastModified ();
        }
        
        /** Mime type of the document.
        * @return the mime type to use for the document
        */
        public String getMimeType() {
            return getFileImpl ().getMIMEType ();
        }
        
        /** First of all tries to lock the primary file and
        * if it succeeds it marks the data object modified.
         * 

Note: There is a contract (better saying a curse) * that this method has to call {@link #takeLock} method * in order to keep working some special filesystem's feature. * See issue #28212. * * @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 * @see org.openide.filesystems.FileObject#isReadOnly */ public void markModified() throws java.io.IOException { // XXX This shouldn't be here. But it is due to the 'contract', // see javadoc to this method. if (fileLock == null || !fileLock.isValid()) { fileLock = takeLock (); } if(getFileImpl().isReadOnly()) { if(fileLock != null && fileLock.isValid()) { fileLock.releaseLock(); } throw new IOException("File " // NOI18N + getFileImpl().getNameExt() + " is read-only!"); // NOI18N } this.getDataObject ().setModified (true); } /** Reverse method that can be called to make the environment * unmodified. */ public void unmarkModified() { if (fileLock != null && fileLock.isValid()) { fileLock.releaseLock(); } this.getDataObject ().setModified (false); } /** Called from the EnvListener * @param expected is the change expected * @param time of the change */ final void fileChanged (boolean expected, long time) { 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 EnvListener. * 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(boolean canBeVetoed) { if (canBeVetoed) { try { // Causes the 'Save' dialog to show if necessary. fireVetoableChange(Env.PROP_VALID, Boolean.TRUE, Boolean.FALSE); } catch(PropertyVetoException pve) { // Ignore it and close anyway. File doesn't exist anymore. } } // Closes the components. firePropertyChange(Env.PROP_VALID, Boolean.TRUE, Boolean.FALSE); } } // end of Env /** Listener on file object that notifies the Env object * that a file has been modified. */ private static final class EnvListener extends FileChangeAdapter { /** Reference (Env) */ private Reference env; /** @param env environement to use */ public EnvListener (Env env) { this.env = new java.lang.ref.WeakReference (env); } /** Handles FileObject deletion event. */ public void fileDeleted(FileEvent fe) { Env env = (Env)this.env.get(); FileObject fo = fe.getFile(); if(env == null || env.getFileImpl() != fo) { // the Env change its file and we are not used // listener anymore => remove itself from the list of listeners fo.removeFileChangeListener(this); return; } fo.removeFileChangeListener(this); // #30210 - when edited file was deleted the "Do you want to save changes" // dialog should not be shown env.fileRemoved(false); fo.addFileChangeListener(this); } /** Fired when a file is changed. * @param fe the event describing context where action has taken place */ public void fileChanged(FileEvent fe) { Env env = (Env)this.env.get (); if (env == null || env.getFileImpl () != fe.getFile ()) { // the Env change its file and we are not used // listener anymore => remove itself from the list of listeners fe.getFile ().removeFileChangeListener (this); return; } // #16403. Added handling for virtual property of the file. if(fe.getFile().isVirtual()) { // Remove file event coming as consequence of this change. fe.getFile().removeFileChangeListener(this); // File doesn't exist on disk -> simulate env is invalid, // even the fileObject could be valid, see VCS FS. env.fileRemoved(true); fe.getFile().addFileChangeListener(this); } else { env.fileChanged (fe.isExpected (), fe.getTime ()); } } } /** Listener on node representing asociated data object, listens to the * property changes of the node and updates state properly */ private final class DataNodeListener extends NodeAdapter { /** Asociated editor */ CloneableEditor editor; DataNodeListener (CloneableEditor editor) { this.editor = editor; } public void propertyChange(final PropertyChangeEvent ev) { Mutex.EVENT.writeAccess(new Runnable() { public void run() { if (Node.PROP_DISPLAY_NAME.equals(ev.getPropertyName())) { updateTitles(); } if (Node.PROP_ICON.equals(ev.getPropertyName())) { if (obj.isValid()) { editor.setIcon(obj.getNodeDelegate().getIcon (java.beans.BeanInfo.ICON_COLOR_16x16)); } } } }); } } // end of DataNodeListener /* Create a special lookup implementation that contains a DataObject and its * primary fileobject. If the file is moved, the FileObject is replaced, * while the DataObject keeps the identity. */ private static Lookup createLookup(final DataObject dobj) { final InstanceContent ic = new InstanceContent(); Lookup l = new AbstractLookup(ic); dobj.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { String propName = ev.getPropertyName(); if (propName == null || propName == DataObject.PROP_PRIMARY_FILE) { updateLookup(dobj, ic); } } }); updateLookup(dobj,ic); return l; } private static void updateLookup(DataObject d, InstanceContent ic) { ic.set(Arrays.asList(new Object[] { d, d.getPrimaryFile() }), null); } }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.