CasaDataEditorSupport.java :  » IDE-Netbeans » compapp » org » netbeans » modules » compapp » casaeditor » Java Open Source

Java Open Source » IDE Netbeans » compapp 
compapp » org » netbeans » modules » compapp » casaeditor » CasaDataEditorSupport.java
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */

package org.netbeans.modules.compapp.casaeditor;

import java.awt.EventQueue;
import java.beans.PropertyVetoException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.netbeans.core.api.multiview.MultiViewHandler;
import org.netbeans.core.api.multiview.MultiViews;
import org.netbeans.core.spi.multiview.CloseOperationHandler;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.modules.compapp.casaeditor.multiview.CasaMultiViewFactory;
import org.openide.cookies.CloseCookie;
import org.openide.cookies.EditCookie;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.LineCookie;
import org.openide.cookies.OpenCookie;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.text.CloneableEditor;
import org.openide.text.CloneableEditorSupport;
import org.openide.text.CloneableEditorSupport.Pane;
import org.openide.text.DataEditorSupport;
import org.openide.util.Task;
import org.openide.windows.CloneableTopComponent;
import org.openide.windows.Mode;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;

import java.io.IOException;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.StyledDocument;
import org.netbeans.api.project.Project;
import org.netbeans.core.api.multiview.MultiViewPerspective;
import org.netbeans.modules.compapp.casaeditor.design.CasaModelGraphScene;
import org.netbeans.modules.compapp.casaeditor.model.casa.CasaComponent;
import org.netbeans.modules.compapp.casaeditor.model.casa.CasaModel;
import org.netbeans.modules.compapp.casaeditor.model.jbi.CasaModelFactory;
import org.netbeans.modules.compapp.casaeditor.model.casa.CasaWrapperModel;
import org.netbeans.modules.compapp.casaeditor.multiview.CasaGraphMultiViewElement;
import org.netbeans.modules.xml.retriever.catalog.Utilities;
//import org.netbeans.modules.xml.validation.ShowCookie;
import org.netbeans.modules.xml.xam.ModelSource;
import org.netbeans.modules.xml.xam.spi.Validator.ResultItem;
import org.netbeans.modules.xml.xam.ui.undo.QuietUndoManager;
import org.openide.ErrorManager;
import org.openide.awt.UndoRedo;
import org.openide.filesystems.FileChangeListener;
import org.openide.util.TaskListener;

/**
 *
 * @author tli
 */
public class CasaDataEditorSupport extends DataEditorSupport
implements 
        /* CasaModelCookie, */ 
        OpenCookie, 
        EditCookie,
        EditorCookie.Observable, 
        LineCookie, 
        CloseCookie
//        ShowCookie
{
    
    /** Used for managing the prepareTask listener. */
    private transient Task prepareTask2;
    
    private transient CasaModelGraphScene mScene;
    
    //private CasaDataObject mDataObject;
    
    /** Needed for casa file deletion. */
    private static Map<DataObject, CasaWrapperModel> modelMap = 
            new HashMap<DataObject, CasaWrapperModel>();
    
    
    public CasaDataEditorSupport(CasaDataObject sobj) {
        super(sobj, new CasaEditorEnv(sobj));
        
        //mDataObject = sobj;
        
        setMIMEType("text/xml");     // NOI18N
        //TODO: we need to use below one eventually
        //setMIMEType(WSDLDataLoader.MIME_TYPE);
    }
    
    
    public CasaEditorEnv getEnv() {
        return (CasaEditorEnv)env;
    }
    
    @Override
    protected Pane createPane() {
        TopComponent tc = CasaMultiViewFactory.createMultiView((CasaDataObject) getDataObject());
        Mode editorMode = WindowManager.getDefault().findMode(CasaDataEditorSupport.EDITOR_MODE);
        if (editorMode != null) {
            editorMode.dockInto(tc);
        }
        return (Pane) tc;
    }
    
    public static boolean isLastView(TopComponent tc) {
        
        if (!(tc instanceof CloneableTopComponent))
            return false;
        
        boolean oneOrLess = true;
        Enumeration en =
                ((CloneableTopComponent)tc).getReference().getComponents();
        if (en.hasMoreElements()) {
            en.nextElement();
            if (en.hasMoreElements())
                oneOrLess = false;
        }
        
        return oneOrLess;
    }
    
    // Change method access to public
    @Override
    public void initializeCloneableEditor(CloneableEditor editor) {
        super.initializeCloneableEditor(editor);
        // Force the title to update so the * left over from when the
        // modified data object was discarded is removed from the title.
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                // Have to do this later to avoid infinite loop.
                updateTitles();
            }
        });
    }
    
    @Override
    protected void updateTitles() {
        // This method is invoked by DataEditorSupport.DataNodeListener
        // whenever the DataNode displayName property is changed. It is
        // also called when the CloneableEditorSupport is (un)modified.
        
        // Let the superclass handle the CloneableEditor instances.
        super.updateTitles();
        
        // We need to get the title updated on the MultiViewTopComponent.
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                // Create a list of TopComponents associated with the
                // editor's data object, starting with the active
                // TopComponent. Add all open TopComponents in any
                // mode that are associated with the DataObject.
                // [Note that EDITOR_MODE does not contain editors in
                // split mode.]
                List<TopComponent> associatedTCs = new ArrayList<TopComponent>();
                DataObject targetDO = getDataObject();
                TopComponent activeTC = TopComponent.getRegistry().getActivated();
                if (activeTC != null && targetDO == (DataObject) activeTC.getLookup().lookup(
                        DataObject.class)) {
                    associatedTCs.add(activeTC);
                }
                Set openTCs = TopComponent.getRegistry().getOpened();
                for (Object tc : openTCs) {
                    TopComponent tcc = (TopComponent) tc;
                    if (targetDO == (DataObject) tcc.getLookup().lookup(
                            DataObject.class)) {
                        associatedTCs.add(tcc);
                    }
                }
                for (TopComponent tc : associatedTCs) {
                    // Make sure this is a multiview window, and not just some
                    // window that has our DataObject (e.g. Projects, Files).
                    MultiViewHandler mvh = MultiViews.findMultiViewHandler(tc);
                    if (mvh != null) {
                        tc.setHtmlDisplayName(messageHtmlName());
                        String name = messageName();
                        tc.setDisplayName(name);
                        tc.setName(name);
                        tc.setToolTipText(messageToolTip());
                        
                        // Make the Source View multiview window un-editable.
                        JEditorPane[] editorPanes = getOpenedPanes();
                        if (editorPanes != null) {
                            for (JEditorPane editorPane : editorPanes) {
                                editorPane.setEditable(false);
                            }
                        }

                    }
                }
            }
        });
    }
    
    @Override
    protected UndoRedo.Manager createUndoRedoManager() {
        // Override so the superclass will use our proxy undo manager
        // instead of the default, then we can intercept edits.
        return new QuietUndoManager(super.createUndoRedoManager());
        // Note we cannot set the document on the undo manager right
        // now, as CES is probably trying to open the document.
    }
    
    /**
     * Returns the UndoRedo.Manager instance managed by this editor support.
     *
     * @return UndoRedo.Manager instance.
     */
    public QuietUndoManager getUndoManager() {
        return (QuietUndoManager) getUndoRedo();
    }
    
    @Override
    public Task prepareDocument() {
        Task task = super.prepareDocument();
        // Avoid listening to the same task more than once.
        if (task != prepareTask2) {
            prepareTask2 = task;
            task.addTaskListener(new TaskListener() {
                public void taskFinished(Task task) {
                    QuietUndoManager undo = getUndoManager();
                    StyledDocument doc = getDocument();
                    synchronized (undo) {
                        // Now that the document is ready, pass it to the manager.
                        undo.setDocument((AbstractDocument) doc);
                        if (!undo.isCompound()) {
                            // The superclass prepareDocument() adds the undo/redo
                            // manager as a listener -- we need to remove it since
                            // we will initially listen to the model instead.
                            doc.removeUndoableEditListener(undo);
                            // If not listening to document, then listen to model.
                            addUndoManagerToModel(undo);
                        }
                    }
                    prepareTask2 = null;
                }
            });
        }
        return task;
    }
    
    public Task reloadDocument() {
        Task task = super.reloadDocument();
        task.addTaskListener(new TaskListener() {
            public void taskFinished(Task task) {
                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        QuietUndoManager undo = getUndoManager();
                        StyledDocument doc = getDocument();
                        // The superclass reloadDocument() adds the undo
                        // manager as an undoable edit listener.
                        synchronized (undo) {
                            if (!undo.isCompound()) {
                                doc.removeUndoableEditListener(undo);
                            }
                        }
                    }
                });
            }
        });
        return task;
    }
    
    protected void notifyClosed() {
        // Stop listening to the undoable edit sources when we are closed.
        QuietUndoManager undo = getUndoManager();
        StyledDocument doc = getDocument();
        synchronized (undo) {
            // May be null when closing the editor.
            if (doc != null) {
                doc.removeUndoableEditListener(undo);
                undo.endCompound();
                undo.setDocument(null);
            }
            try {
                CasaWrapperModel model = getModel();
                if (model != null) {
                    model.removeUndoableEditListener(undo);
                }
                // Must unset the model when no longer listening to it.
                undo.setModel(null);
            } catch (Exception ioe) {
                // Model is gone, but just removing the listener is not
                // going to matter anyway.
            }
        }
     
        super.notifyClosed();
    }
    
    // When the scene is created, it is set here for access later.
    public void setScene(CasaModelGraphScene scene) {
        mScene = scene;
    }
    
    // Provides access to the scene, for convenience.
    public CasaModelGraphScene getScene() {
        return mScene;
    }
    
    public CasaWrapperModel getModel() {
        CasaDataObject dobj = getEnv().getCasaDataObject();
        return getModel(dobj);
    }
    
    private static CasaWrapperModel getModel(DataObject dobj) {
        CasaWrapperModel model = modelMap.get(dobj);
        if (model == null) {
            ModelSource modelSource = Utilities.getModelSource(dobj.getPrimaryFile(), true);
            if (modelSource != null) {
                model = (CasaWrapperModel) CasaModelFactory.getInstance().getModel(modelSource);
                modelMap.put(dobj, model);               
            }
        }
        return model;
    }
    
    /**
     * Adds the undo/redo manager to the document as an undoable edit
     * listener, so it receives the edits onto the queue. The manager
     * will be removed from the model as an undoable edit listener.
     *
     * <p>This method may be called repeatedly.</p>
     */
    public void addUndoManagerToDocument() {
        // This method may be called repeatedly.
        // Stop the undo manager from listening to the model, as it will
        // be listening to the document now.
        QuietUndoManager undo = getUndoManager();
        StyledDocument doc = getDocument();
        synchronized (undo) {
            try {
                CasaWrapperModel model = getModel();
                if (model != null) {
                    model.removeUndoableEditListener(undo);
                }
                // Must unset the model when no longer listening to it.
                undo.setModel(null);
            } catch (Exception ioe) {
                // Model is gone, but just removing the listener is not
                // going to matter anyway.
            }
            // Document may be null if the cloned views are not behaving correctly.
            if (doc != null) {
                // Ensure the listener is not added twice.
                doc.removeUndoableEditListener(undo);
                doc.addUndoableEditListener(undo);
                // Start the compound mode of the undo manager, such that when
                // we are hidden, we will treat all of the edits as a single
                // compound edit. This avoids having the user invoke undo
                // numerous times when in the model view.
                undo.beginCompound();
            }
        }
    }
    
    /**
     * Removes the undo/redo manager undoable edit listener from the
     * document, to stop receiving undoable edits. The manager will
     * be added to the model as an undoable edit listener.
     *
     * <p>This method may be called repeatedly.</p>
     */
    public void removeUndoManagerFromDocument() {
        // This method may be called repeatedly.
        QuietUndoManager undo = getUndoManager();
        StyledDocument doc = getDocument();
        synchronized (undo) {
            // May be null when closing the editor.
            if (doc != null) {
                doc.removeUndoableEditListener(undo);
                undo.endCompound();
            }
            // Have the undo manager listen to the model when it is not
            // listening to the document.
            addUndoManagerToModel(undo);
        }
    }
    
    /**
     * Add the undo/redo manager undoable edit listener to the model.
     *
     * <p>Caller should synchronize on the undo manager prior to calling
     * this method, to avoid thread concurrency issues.</p>
     *
     * @param  undo  the undo manager.
     */
    private void addUndoManagerToModel(QuietUndoManager undo) {
        // This method may be called repeatedly.
        try {
            CasaWrapperModel model = getModel();
            if (model != null) {
                // Ensure the listener is not added twice.
                model.removeUndoableEditListener(undo);
                model.addUndoableEditListener(undo);
                // Ensure the model is sync'd when undo/redo is invoked,
                // otherwise the edits are added to the queue and eventually
                // cause exceptions.
                undo.setModel(model);
            }
        } catch (Exception ioe) {
            // Model is gone, nothing will work, return immediately.
        }
    }
    
    /**
     * This method allows the close behavior of CloneableEditorSupport to be
     * invoked from the SourceMultiViewElement. The close method of
     * CloneableEditorSupport at least clears the undo queue and releases
     * the swing document.
     */
    public boolean silentClose() {
        return super.close(false);
    }
    
    /**
     * Implement ShowCookie.
     */
    public void show(final ResultItem resultItem) {
        if (!(resultItem.getModel() instanceof CasaModel))
            return;

        final CasaComponent casaComponent = 
                (CasaComponent) resultItem.getComponents();

        // Get the edit and line cookies.
        DataObject d = getDataObject();
        LineCookie lc = (LineCookie) d.getCookie(LineCookie.class);
        final EditCookie ec = (EditCookie) d.getCookie(EditCookie.class);
        if (lc == null || ec == null) {
            return;
        }

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                // Opens the editor or brings it into focus
                // and makes it the activated topcomponent.
                ec.edit();

                TopComponent tc = WindowManager.getDefault().getRegistry()
                        .getActivated();
                MultiViewHandler mvh = MultiViews.findMultiViewHandler(tc);

                if (mvh == null) {
                    return;
                }

                // Set annotation or select element in the multiview.
                MultiViewPerspective mvp = mvh.getSelectedPerspective();
                if (mvp.preferredID().equals("casa-graphview")) { // NOI18N
                    List<TopComponent> list = getAssociatedTopComponents();
                    for (TopComponent topComponent : list) {
                        // Make sure this is a multiview window, and not just
                        // some window that has our DataObject (e.g. Projects,
                        // Files).
                        MultiViewHandler handler = MultiViews
                                .findMultiViewHandler(topComponent);
                        if (handler != null && topComponent != null) {
                            topComponent.getLookup().lookupAll(Object.class);
                            CasaGraphMultiViewElement selector =
                                    topComponent.getLookup().lookup(
                                    CasaGraphMultiViewElement.class); // FIXME: add new interface
                            if (selector == null)
                                return;
                            selector.select(casaComponent);
                        }
                    }
                }
            }
        });
    }    
    
    private List<TopComponent> getAssociatedTopComponents() {
        // Create a list of TopComponents associated with the
        // editor's schema data object, starting with the the
        // active TopComponent. Add all open TopComponents in
        // any mode that are associated with the DataObject.
        // [Note that EDITOR_MODE does not contain editors in
        // split mode.]
        List<TopComponent> associatedTCs = new ArrayList<TopComponent>();
        DataObject targetDO = getDataObject();
        TopComponent activeTC = TopComponent.getRegistry().getActivated();
        if (activeTC != null
                && targetDO == activeTC.getLookup().lookup(DataObject.class)) {
            associatedTCs.add(activeTC);
        }
        Set openTCs = TopComponent.getRegistry().getOpened();
        for (Object tc : openTCs) {
            TopComponent tcc = (TopComponent) tc;
            if (targetDO == tcc.getLookup().lookup(DataObject.class)) {
                associatedTCs.add(tcc);
            }
        }
        return associatedTCs;
    }
    
    /** {@inheritDoc} */
    public void saveDocument() throws IOException {
        DataObject casaDO = getDataObject();
        FileObject casaFO = casaDO.getPrimaryFile();
        CasaWrapperModel model = getModel();
        Project project = model.getJBIProject();
        FileChangeListener listener =
                    project.getLookup().lookup(FileChangeListener.class);
        if (listener != null) {
            casaFO.removeFileChangeListener(listener);
            casaFO.addFileChangeListener(listener);
        }
        
        super.saveDocument();
        
        model.saveDocument();
        
//        syncModel();
        casaDO.setModified(false);
    }
    
    /**
     * Have the Casa model sync with the document.
     */
    public void syncModel() {
        // Only sync the document if the change relates to loss of focus,
        // which indicates that we are switching from the source view.
        // Update the tree with the modified text.
        try {
            if (getModel() != null) {
                 getModel().sync();
            }
        } catch (Throwable ioe) {
// initially, for debug purposes, just print out the exception
ioe.printStackTrace();
ErrorManager.getDefault().notify(ioe);

//            NotifyDescriptor nd = new NotifyDescriptor.Message(
//                    NbBundle.getMessage(
//                    CasaDataEditorSupport.class, 
//                    "MSG_NotWellformedXml"), 
//                    NotifyDescriptor.ERROR_MESSAGE);
//            DialogDisplayer.getDefault().notify(nd);
        }
    }
    
    
    ////////////////////////////////////////////////////////////////////////////
    // Inner class
    ////////////////////////////////////////////////////////////////////////////
    
    /**
     * Env class extends DataEditorSupport.Env.
     */
    protected static class CasaEditorEnv extends DataEditorSupport.Env {
        
        static final long serialVersionUID = -4541024350167923282L;
        
        public CasaEditorEnv(CasaDataObject obj) {
            super(obj);
        }
        
        public CloneableEditorSupport findTextEditorSupport() {
            return getCasaDataObject().getEditorSupport();
        }
        
        public CasaDataObject getCasaDataObject(){
            return (CasaDataObject) getDataObject();
        }
        
        @Override
        protected FileObject getFile() {
            return getDataObject().getPrimaryFile();
        }
        
        @Override
        protected FileLock takeLock() throws IOException {
            return getDataObject().getPrimaryFile().lock();
        }
    }
    

    ////////////////////////////////////////////////////////////////////////////
    // Inner class
    ////////////////////////////////////////////////////////////////////////////
    
    /**
     * Implementation of CloseOperationHandler for multiview. Ensures both
     * column view and xml editor are correctly closed, data saved, etc. Holds
     * a reference to DataObject only - to be serializable with the
     * multiview TopComponent without problems.
     */
    public static class CloseHandler implements CloseOperationHandler, Serializable {
        static final long serialVersionUID = -3085696462320564244L;
        private DataObject myDataObject;

        private CloseHandler() {
            super();
        }

        public CloseHandler(DataObject dobj) {
            myDataObject = dobj;
        }

        private CasaDataEditorSupport getCasaDataEditorSupport() {
            return myDataObject instanceof CasaDataObject ?
                ((CasaDataObject) myDataObject).getEditorSupport() : null;
        }
        
        public boolean resolveCloseOperation(CloseOperationState[] elements) {
            CasaDataEditorSupport editor = getCasaDataEditorSupport();
      boolean canClose = editor != null ? editor.canClose() : true;
      // during the shutdown sequence this is called twice. The first time
      // through the multi-view infrastructure. The second time is done through
      // the TopComponent close. If the file is dirty and the user chooses
      // to discard changes, the second time will also ask whether the
      // to save or discard changes. 
      if (canClose) {
    myDataObject.setModified(false);
                
                // discard changes to related models
                CasaWrapperModel model = getModel(myDataObject);
                if (model != null) {
                    // when deleting casa file, the model will be null at
                    // the second time. The related data objects should have
                    // been discarded at the first time.
                    model.discardRelatedDataObjects();
                }
                
                // invalidate the in-memory copy of this data object 
                try {
                    myDataObject.setValid(false);
                } catch (PropertyVetoException ex) {
                    ex.printStackTrace();
                }
                
                modelMap.remove(myDataObject);
      }
      return canClose;
        }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.