All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.properties.PropertiesOpen Maven / Gradle / Ivy

/*
 * 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.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionListener;
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.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.event.ChangeListener;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;

import org.openide.actions.FindAction;
import org.openide.awt.UndoRedo;
import org.openide.cookies.CloseCookie;
import org.openide.cookies.OpenCookie;
import org.openide.cookies.SaveCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileStatusEvent;
import org.openide.filesystems.FileStatusListener;
import org.openide.filesystems.FileSystem;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.Node;
import org.openide.NotifyDescriptor;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;
import org.openide.nodes.Node.Cookie;
import org.openide.util.Exceptions;
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.util.WeakSet;
import org.openide.util.actions.SystemAction;
import org.openide.windows.CloneableOpenSupport;
import org.openide.windows.CloneableTopComponent;
import org.openide.DialogDescriptor;
import org.openide.text.DataEditorSupport;


/** 
 * Support for opening bundle of .properties files (OpenCookie) in table view editor. 
 * 
 * @author Petr Jiricka, Peter Zavadsky
 */
public class PropertiesOpen extends CloneableOpenSupport
                            implements OpenCookie, CloseCookie {

    private static final Logger LOG = Logger.getLogger(PropertiesOpen.class.getName());
    /** Main properties dataobject */
    @Deprecated
    PropertiesDataObject propDataObject;


    private List dataObjectList;

    private BundleStructure bundleStructure;
    /** Listener for modificationc on dataobject, adding and removing save cookie */
    PropertyChangeListener modifL;

    HashMap weakModifiedListeners;

    /** UndoRedo manager for this properties open support */
    protected transient UndoRedo undoRedoManager;

    /** This object is used for marking all undoable edits performed as one atomic undoable action. */
    transient Object atomicUndoRedoFlag;

    private transient Object UPDATE_LOCK = new Object();
    

    /** Constructor */
    @Deprecated
    public PropertiesOpen(PropertiesDataObject propDataObject) {
        super(new Environment(propDataObject));
        
        this.propDataObject = propDataObject;

        //PENDING Add Listeners for all DataObject from this OpenSupport
        this.propDataObject.addPropertyChangeListener(WeakListeners.propertyChange(modifL =
            new ModifiedListener(), this.propDataObject));
    }

    public PropertiesOpen(BundleStructure structure) {
        super(new Environment(structure));

        this.bundleStructure = structure;
        //Listeners added later in addDataObject method
//        addModifiedListeners();
        dataObjectList = new ArrayList();
        weakModifiedListeners = new HashMap();
        modifL = new ModifiedListener();
    }

    protected void removeModifiedListener(PropertiesDataObject dataObject) {
        PropertyChangeListener l = weakModifiedListeners.remove(dataObject);
        if (l!=null) {
            dataObject.removePropertyChangeListener(l);
        }
        PropertiesCloneableTopComponent topComp = (PropertiesCloneableTopComponent) allEditors.getArbitraryComponent();
        if (topComp != null) {
            topComp.dataObjectRemoved(dataObject);
        }
        dataObjectList.remove(dataObject);
    }

    protected void addDataObject(PropertiesDataObject dataObject) {
            PropertyChangeListener l = weakModifiedListeners.get(dataObject);
            if (l != null) {
                dataObject.removePropertyChangeListener(l);
            } else {
                l = WeakListeners.propertyChange(modifL, dataObject);
                weakModifiedListeners.put(dataObject,l);
            }
            dataObject.addPropertyChangeListener(l);
            ((Environment)env).addListener(dataObject);
            if (((Environment)env).isModified())
                try {
                   ((Environment) env).markModified(dataObject);
                   if (dataObject.getCookie(SaveCookie.class) == null) {
                       dataObject.getCookieSet0().add((Cookie) modifL);
                   }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            PropertiesCloneableTopComponent topComp = (PropertiesCloneableTopComponent) allEditors.getArbitraryComponent();
            if (topComp != null) {
                topComp.dataObjectAdded(dataObject);
            }
        if (!dataObjectList.contains(dataObject)) {
            dataObjectList.add(dataObject);
        }
    }
    /** 
     * Tests whether all data is saved, and if not, prompts the user to save.
     *
     * @return {@code true} if everything can be closed
     */
    @Override
    protected boolean canClose() {
        PropertiesDataObject dataObject;
        SaveCookie saveCookie = null;
        HashMap map = new HashMap();
        for (PropertiesFileEntry e : bundleStructure.getEntries()) {
            dataObject = (PropertiesDataObject) e.getDataObject();
            saveCookie = dataObject.getCookie(SaveCookie.class);
            //Need to find all saveCookie
            if (saveCookie != null) map.put(saveCookie, dataObject);
        }
        if (map.isEmpty()) {
            return true;
        }
        stopEditing();
        if (!shouldAskSave()) {
            return true;
        }
        
        /* Create and display a confirmation dialog - Save/Discard/Cancel: */
        String title = NbBundle.getMessage(PropertiesOpen.class,
                                           "CTL_Question");         //NOI18N
        String question = NbBundle.getMessage(PropertiesOpen.class,
                                              "MSG_SaveFile",       //NOI18N
                                              bundleStructure.getNthEntry(0).getName());
        String optionSave = NbBundle.getMessage(PropertiesOpen.class,
                                                "CTL_Save");        //NOI18N
        String optionDiscard = NbBundle.getMessage(PropertiesOpen.class,
                                                   "CTL_Discard");  //NOI18N
        NotifyDescriptor descr = new DialogDescriptor(
                question,
                title,                              //title
                true,                               //modal
                new Object[] {optionSave,
                              optionDiscard,
                              NotifyDescriptor.CANCEL_OPTION},
                optionSave,                         //default option
                DialogDescriptor.DEFAULT_ALIGN,     //alignment of the options
                null,                               //help context
                (ActionListener) null);
        descr.setMessageType(NotifyDescriptor.QUESTION_MESSAGE);
        Object answer = DialogDisplayer.getDefault().notify(descr);
        
        /* Save the file if the answer was "Save": */
        if (answer == optionSave) {
            try {
                for (SaveCookie save : map.keySet()) {
                    save.save();
                    map.get(save).updateModificationStatus();
                }
            }
            catch (IOException e) {
                ErrorManager.getDefault().notify(e);
                return false;
            }
        }
        dataObject = null;
        for (int i=0;i don't close document.
            return;
        } else {
            // Hasn't opened editor view for this entry -> close document.
            editorSupport.forceNotifyClosed();
            
            // #17221. Don't reparse invalid or virtual file.
            if(entry.getFile().isValid() && !entry.getFile().isVirtual()) {
                entry.getHandler().autoParse();
            }
        }
    }

    /**
     * Helper method. Should be called only if the object has SaveCookie
     * @return true if closing this editor whithout saving would result in loss of data
     *  because al least one of the modified files is not open in the code editor
     */
    private boolean shouldAskSave() {
        // for each entry : if there is a SaveCookie and no open editor component, return true.
        // if passed for all entries, return false
        BundleStructure structure = bundleStructure;
        PropertiesFileEntry entry;
        SaveCookie savec;
        for (int i = 0; i < structure.getEntryCount(); i++) {
            entry = structure.getNthEntry(i);
            savec = entry.getCookie(SaveCookie.class);
            if ((savec != null) && !entry.getPropertiesEditor().hasOpenedEditorComponent()) {
                return true;
            }
        }
        return false;
    }


    /** Environment that connects the open support together with {@code DataObject}. */
    private static class Environment implements CloneableOpenSupport.Env, Serializable,
        PropertyChangeListener, VetoableChangeListener {
            
        /** Generated Serialized Version UID */
        static final long serialVersionUID = -1934890789745432531L;
        
        /** Object to serialize and be connected to. */
        @Deprecated
        private DataObject dataObject;

        private BundleStructure bundleStructure;
        
        /** Support for firing of property changes. */
        private transient PropertyChangeSupport propSupp;
        
        /** Support for firing of vetoable changes. */
        private transient VetoableChangeSupport vetoSupp;

        private transient HashMap weakEnvPropListeners;
        private transient HashMap weakEnvVetoListeners;
        
        /** 
         * Constructor. Attaches itself as listener to 
         * the data object so, all property changes of the data object
         * are also rethrown to own listeners.
         * @param dataObject data object to be attached to
         */
        @Deprecated
        public Environment(PropertiesDataObject dataObject) {
            this.dataObject = dataObject;
            dataObject.addPropertyChangeListener(WeakListeners.propertyChange(this, dataObject));
            dataObject.addVetoableChangeListener(WeakListeners.vetoableChange(this, dataObject));
        }

        public Environment(BundleStructure structure) {
            this.bundleStructure = structure;
            PropertiesFileEntry entry = bundleStructure.getNthEntry(0);
            if (entry != null)
                dataObject = entry.getDataObject();
            //Listeners added later by addListener method
//            addListeners();
            weakEnvPropListeners = new HashMap();
            weakEnvVetoListeners = new HashMap();
        }


        private void addListener(PropertiesDataObject dataObj) {
            PropertyChangeListener l =weakEnvPropListeners.get(dataObj);
            VetoableChangeListener v = weakEnvVetoListeners.get(dataObj);
            if (l != null) {
                dataObj.removePropertyChangeListener(l);
            } else {
                l = WeakListeners.propertyChange(this, dataObj);
                weakEnvPropListeners.put(dataObj, l);
            }
            if (v != null) {
                dataObj.removeVetoableChangeListener(v);
            } else {
                v = WeakListeners.vetoableChange(this, dataObj);
                weakEnvVetoListeners.put(dataObj, v);
            }
            dataObj.addPropertyChangeListener(l);
            dataObj.addVetoableChangeListener(v);
        }

        private  void addListeners() {
            BundleStructure structure = bundleStructure;
            PropertiesDataObject dataObj;
            weakEnvPropListeners = new HashMap();
            weakEnvVetoListeners = new HashMap();
            PropertyChangeListener l;
            VetoableChangeListener v;
            for(int i=0;i1) {
                            dataObj.getBundleStructure().updateEntries();
                            dataObj.getBundleStructure().notifyOneFileChanged(dataObj.getPrimaryFile());
                            dataObj.getOpenSupport().open();
                        }
                    }
                }
            } else if (DataObject.PROP_PRIMARY_FILE.equals(evt.getPropertyName())) {
                //Here DataObject is valid and with new path
                PropertiesDataObject dataObj = null;
                if (evt.getSource() instanceof PropertiesDataObject) {
                    dataObj = (PropertiesDataObject) evt.getSource();
                }
                if (dataObj != null) {
                    OpenCookie cookie = dataObj.getCookieSet0().getCookie(OpenCookie.class);
                    if (cookie != dataObj.getOpenSupport()) {
                        PropertyChangeListener l = weakEnvPropListeners.remove(dataObj);
                        VetoableChangeListener v = weakEnvVetoListeners.remove(dataObj);
                        if (l!=null) {
                            dataObj.removePropertyChangeListener(l);
                        }
                        if (v!=null) {
                            dataObj.removeVetoableChangeListener(v);
                        }
                        // remove old open cookie
                        if (cookie != null) {
                            // remove also open cookie factory, new open cookie will be added.
                            dataObj.getCookieSet0().remove(PropertiesOpen.class, dataObj);
                            dataObj.getCookieSet0().remove(cookie);
                        }
                        //Adds new OpenCookie to this dataObj
                        dataObj.getCookieSet0().add(dataObj.getOpenSupport());
                        if (dataObj.getBundleStructure().getEntryCount()>1) {
                            dataObj.getBundleStructure().updateEntries();
                            dataObj.getBundleStructure().notifyOneFileChanged(dataObj.getPrimaryFile());
                        }
                    } else {
                        //shouldn't get here
                    }
                }
            } else {
                firePropertyChange (
                    evt.getPropertyName(),
                    evt.getOldValue(),
                    evt.getNewValue()
                );
            }
        }
        
        /**
         * Implements VetoAbleChangeListener interface. 
         * Accepts vetoable changes and fires them to own listeners.
         */
        @Override
        public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
            fireVetoableChange (
                evt.getPropertyName(),
                evt.getOldValue(),
                evt.getNewValue()
            );
        }
        
        /** 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 gets property change support. */
        private PropertyChangeSupport prop() {
            synchronized (this) {
                if (propSupp == null) {
                    propSupp = new PropertyChangeSupport(this);
                }
            }
            return propSupp;
        }
        
        /** Lazy gets vetoable change support. */
        private VetoableChangeSupport veto() {
            synchronized (this) {
                if (vetoSupp == null) {
                    vetoSupp = new VetoableChangeSupport(this);
                }
            }
            return vetoSupp;
        }
    } // End of inner class Environment.
    
    
    /** Inner class. Listens to modifications and updates save cookie. */
    private final class ModifiedListener implements SaveCookie, PropertyChangeListener {


        /** Gives notification that the DataObject was changed.
        * @param ev PropertyChangeEvent
        */
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // Data object changed, reset the UndoRedo manager.
            if (DataObject.PROP_MODIFIED.equals(evt.getPropertyName())) {
                if (evt.getSource() instanceof  PropertiesDataObject) {
                    PropertiesDataObject dataObject = (PropertiesDataObject) evt.getSource();
                    if (!dataObject.isValid()) {
                            if (!((Boolean)evt.getNewValue()).booleanValue()) {
                                removeSaveCookie(dataObject);
                            }
                    } else
                    if(bundleStructure.getEntryByFileName(dataObject.getName())!=null) {
                        ((CompoundUndoRedoManager) PropertiesOpen.this.getUndoRedo()).reset(bundleStructure);
                            if (((Boolean)evt.getNewValue()).booleanValue()) {
                                addSaveCookie(dataObject);
                            } else {
                                removeSaveCookie(dataObject);
                            }
                     }
                }
            }
        }

        /** Implements {@code SaveCookie} interface. */
        @Override
        public void save() throws IOException {
            /*
             * At first, save the value of the cell being edited,
             * without making any UI changes.
             */
            saveEditorValues(false);

            // do saving job
            saveDocument();

            /* Update the UI: */
            Mutex.EVENT.writeAccess(new Runnable() {
                @Override
                public void run() {
                    stopEditing();
                }
            });
        }

        /** Save the document in this thread.
        * Create "orig" document for the case that the save would fail.
        * @exception IOException on I/O error
        */
        public void saveDocument() throws IOException {
            BundleStructure structure = bundleStructure;
            SaveCookie save;
            for (int i=0; i dataObjectsList;
        /** Listener for changes on {@code propDataObject} name and cookie properties.
         * Changes display name of components accordingly. */
        private transient PropertyChangeListener nameUpdaterListener;

        private transient static HashMap weakNameUpdateListeners;

        /** Generated serial version UID. */
        static final long serialVersionUID =2836248291419024296L;
        
        private final static transient Object UPDATE_LOCK = new Object();
        /** Default constructor for deserialization. */
        public PropertiesCloneableTopComponent() {
        }

        @Override
        protected void componentHidden() {
            ((BundleEditPanel)getComponent(0)).getTable().firePropertyChange("componentHidden", 0, 1);  //NOI18N
            super.componentHidden();
        }

        /** Constructor.
        * @param propDataObject data object we belong to */
        @Deprecated
        public PropertiesCloneableTopComponent (PropertiesDataObject propDataObject) {
            this.propDataObject  = propDataObject;

            initialize();
        }

        private MultiBundleStructure bundleStructure;

        public PropertiesCloneableTopComponent(BundleStructure structure) {
            this.bundleStructure = (MultiBundleStructure) structure;
            propDataObject = (PropertiesDataObject) bundleStructure.getNthEntry(0).getDataObject();
            dataObjectsList = new ArrayList();
            for (int i=0; i();
            initialize();
        }
        /**
         */
        @Override
        public void open() {
            if (discard()) {
                return;
            }
            super.open();
        }

        @Override
        public void requestActive() {
            super.requestActive();
            getComponent(0).requestFocusInWindow();
        }
        
        @Override
        public boolean canClose () {
            ((BundleEditPanel)getComponent(0)).stopEditing();
            return super.canClose();
        }
        
        /** Initializes this instance. Used by construction and deserialization. */
        private void initialize() {
            initComponents();
            setupActions();
            BundleStructure structure = bundleStructure;
            PropertiesDataObject dataObject;

            Node[] node = new Node[structure.getEntryCount()];
            nameUpdaterListener = new NameUpdater();

            for( int i=0; i en = getReference().getComponents();
            while (en.hasMoreElements()) {
                CloneableTopComponent tc = en.nextElement();
                tc.setName(name);
                tc.setDisplayName(displayName);
                tc.setHtmlDisplayName(htmlDisplayName);
                tc.setToolTipText(toolTip);
            }
        }
        
        /**
         */
        private void updateDisplayName() {
            assert EventQueue.isDispatchThread();
            
            final String displayName = displayName();
            final String htmlDisplayName = htmlDisplayName();
            final String toolTip = messageToolTip();
            
            Enumeration en = getReference().getComponents();
            while (en.hasMoreElements()) {
                CloneableTopComponent tc = en.nextElement();
                tc.setDisplayName(displayName);
                tc.setHtmlDisplayName(htmlDisplayName);
                tc.setToolTipText(toolTip);
            }
        }
        
        private boolean isModified() {
            PropertiesFileEntry entry;
            for (int i=0;i")) {                //NOI18N
                    displayName = "" + displayName;               //NOI18N
                }
            } else {
                displayName = node.getDisplayName();
            }
            return DataEditorSupport.annotateName(displayName, true, isModified(), false);
        }
        
        /** Gets string for tooltip. */
        private String messageToolTip() {
            FileObject fo = bundleStructure.getNthEntry(0).getFile();
            return DataEditorSupport.toolTip(fo, isModified(), false);
        }
        
        /**
         * 
         * Overrides superclass method. When closing last view, also close the document.
         * @return {@code true} if close succeeded
         */
        @Override
        protected boolean closeLast () {
            if (!bundleStructure.getOpenSupport().canClose ()) {
                // if we cannot close the last window
                return false;
            }
            bundleStructure.getOpenSupport().closeDocuments();
            PropertyChangeListener l;
            for (PropertiesDataObject dataObject:dataObjectsList) {
                l = weakNameUpdateListeners.get(dataObject);
                if (l!=null) {
                    dataObject.removePropertyChangeListener(l);
                    weakNameUpdateListeners.remove(dataObject);
                }
            }
            return true;
        }

        /**
         * Is called from the superclass {@code clone} method to create new component from this one.
         * This implementation only clones the object by calling super.clone method.
         * @return the copy of this object
         */
        @Override
        protected CloneableTopComponent createClonedObject () {
            return new PropertiesCloneableTopComponent(bundleStructure);
        }

        /** Gets {@code Icon}. */
        @Override
        public Image getIcon () {
            return ImageUtilities.loadImage("org/netbeans/modules/properties/propertiesEditorMode.gif"); // NOI18N
        }

        /** Gets help context. */
        @Override
        public HelpCtx getHelpCtx () {
            return new HelpCtx(Util.HELP_ID_MODIFYING);
        }
        
        @Override
        protected String preferredID() {
            return getName();
        }
        
        @Override
        public int getPersistenceType() {
            return PERSISTENCE_ONLY_OPENED;
        }
        
        /** 
         * Gets compound UndoRedo manager from all UndozRedo managers from all editor supports. 
         */
        @Override
        public UndoRedo getUndoRedo () {
            return  bundleStructure.getOpenSupport().getUndoRedo();
        }

        /** Inits the subcomponents. Sets layout for this top component and adds {@code BundleEditPanel} to it. 
         * @see BundleEditPanel */
        private void initComponents() {
            GridBagLayout gridbag = new GridBagLayout();
            setLayout(gridbag);

            GridBagConstraints c = new GridBagConstraints();
            c.fill = GridBagConstraints.BOTH;
            c.weightx = 1.0;
            c.weighty = 1.0;
            c.gridwidth = GridBagConstraints.REMAINDER;
            JPanel panel = new BundleEditPanel(bundleStructure, new PropertiesTableModel(bundleStructure));
            gridbag.setConstraints(panel, c);
            add(panel);
        }
        
        /** This component should be discarded if the associated environment
         *  is not valid.
         */
        private boolean discard () {
            return bundleStructure == null;
        }
        

        /**
         * Serialize this top component.
         * Subclasses wishing to store state must call the super method, then write to the stream.
         * @param out the stream to serialize to
         */
        @Override
        public void writeExternal (ObjectOutput out) throws IOException {
            super.writeExternal(out);
            out.writeObject(bundleStructure.getNthEntry(0).getDataObject());
        }

        /** 
         * Deserialize this top component.
         * Subclasses wishing to store state must call the super method, then read from the stream.
         * @param in the stream to deserialize from
         */
        @Override
        public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException {
            super.readExternal(in);

            propDataObject = (PropertiesDataObject)in.readObject();
            bundleStructure = (MultiBundleStructure) propDataObject.getBundleStructure();
            dataObjectsList = new ArrayList();
            for (int i=0;i();
            initialize();
        }
    } // End of nested class PropertiesCloneableTopComponent.

    /**
     * {@code UndoRedo} manager for {@code PropertiesOpen} support. It contains weak references
     * to all UndoRedo managers from all PropertiesEditor supports (for each entry of dataobject one manager).
     * It uses it's "timeStamp" methods to find out which one of these managers comes to play.
     */
    private static class CompoundUndoRedoManager implements UndoRedo {
        
        /** Set of weak references to all "underlying" editor support undoredo managers. */
        private WeakSet managers = new WeakSet(5);
        
        // Constructor
        
        /** Collects all UndoRedo managers from all editor support of all entries. */
        @Deprecated
        public CompoundUndoRedoManager(PropertiesDataObject obj) {
            init(obj);
        }
        public CompoundUndoRedoManager(BundleStructure structure) {
            init(structure);
        }

        /** Initialize set of managers. */
        @Deprecated
        private void init(PropertiesDataObject obj) {
            BundleStructure structure = obj.getBundleStructure();
            PropertiesEditorSupport editorSupport = null;
            for(int i=0; i< structure.getEntryCount(); i++) {
                editorSupport = structure.getNthEntry(i).getPropertiesEditor();
                if (editorSupport != null) {
                    managers.add(editorSupport.getUndoRedoManager());
                }
            }
        }

        private void init(BundleStructure structure) {
            PropertiesFileEntry entry;
            for(int i=0; i< structure.getEntryCount(); i++) {
                entry = structure.getNthEntry(i);
                if (entry != null)
                    managers.add(entry.getPropertiesEditor().getUndoRedoManager());
            }
        }

        /** Resets the managers. Used when data object has changed. */
        @Deprecated
        public synchronized void reset(PropertiesDataObject obj) {
            managers.clear();
            init(obj);
        }

        public synchronized void reset(BundleStructure structure) {
            managers.clear();
            init(structure);
        }

        /** Gets manager which undo edit comes to play.*/
        private UndoRedo getNextUndo() {
            UndoRedo chosenManager = null;
            long time = 0L; // time to compare with
            long timeManager; // time of next undo of actual manager
            
            for (Iterator it = managers.iterator(); it.hasNext(); ) {
                PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next();
                timeManager = manager.getTimeStampOfEditToBeUndone();
                if(timeManager > time) {
                    time = timeManager;
                    chosenManager = manager;
                }
            }
            return chosenManager;
        }
        
        /** Gets manager which redo edit comes to play.*/
        private UndoRedo getNextRedo() {
            UndoRedo chosenManager = null;
            long time = 0L; // time to compare with
            long timeManager; // time of next redo of actual manager
            
            for (Iterator it = managers.iterator(); it.hasNext(); ) {
                PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next();
                timeManager = manager.getTimeStampOfEditToBeRedone();
                if(timeManager > time) {
                    time = timeManager;
                    chosenManager = manager;
                }
            }
            return chosenManager;
        }
        
        /** Implements {@code UndoRedo}. Test whether at least one of managers can Undo.
         * @return {@code true} if undo is allowed
         */
        @Override
        public synchronized boolean canUndo () {
            for (Manager manager : managers) {
                if (manager.canUndo()) {
                    return true;
                }
            }
            return false;
        }

        /** Implements {@code UndoRedo}. Test whether at least one of managers can Redo.
         * @return {@code true} if redo is allowed
         */
        @Override
        public synchronized boolean canRedo () {
            for (Manager manager : managers) {
                if (manager.canRedo()) {
                    return true;
                }
            }
            return false;
        }

        /** Implements {@code UndoRedo}. Undo an edit. It finds a manager which next undo edit has the highest 
         * time stamp and makes undo on it.
         * @exception CannotUndoException if it fails
         */
        @Override
        public synchronized void undo () throws CannotUndoException {
            PropertiesEditorSupport.UndoRedoStampFlagManager chosenManager = (PropertiesEditorSupport.UndoRedoStampFlagManager)getNextUndo();

            if (chosenManager == null) {
                throw new CannotUndoException();
            } else {
                Object atomicFlag = chosenManager.getAtomicFlagOfEditToBeUndone();
                if (atomicFlag == null) {// not linked with other edits as one atomic action
                    chosenManager.undo();
                } else { // atomic undo compound from more edits in underlying managers
                    boolean undone;
                    do { // the atomic action can consists from more undo edits from same manager
                        undone = false;
                        for (Iterator it = managers.iterator(); it.hasNext(); ) {
                            PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next();
                            if(atomicFlag.equals(manager.getAtomicFlagOfEditToBeUndone())) {
                                manager.undo();
                                undone = true;
                            }
                        }
                    } while(undone);
                }
            }
        }

        /** Implements {@code UndoRedo}. Redo a previously undone edit. It finds a manager which next undo edit has the highest 
         * time stamp and makes undo on it.
         * @exception CannotRedoException if it fails
         */
        @Override
        public synchronized void redo () throws CannotRedoException {
            PropertiesEditorSupport.UndoRedoStampFlagManager chosenManager = (PropertiesEditorSupport.UndoRedoStampFlagManager)getNextRedo();

            if (chosenManager == null) {
                throw new CannotRedoException();
            } else {
                Object atomicFlag = chosenManager.getAtomicFlagOfEditToBeRedone();
                if (atomicFlag == null) {// not linked with other edits as one atomic action
                    chosenManager.redo();
                } else { // atomic redo compound from more edits in underlying managers
                    boolean redone;
                    do { // the atomic action can consists from more redo edits from same manager
                        redone = false;
                        for (Iterator it = managers.iterator(); it.hasNext(); ) {
                            PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next();
                            if(atomicFlag.equals(manager.getAtomicFlagOfEditToBeRedone())) {
                                manager.redo();
                                redone = true;
                            }
                        }
                    } while(redone);
                }
            }
        }

        /** Implements {@code UndoRedo}. Empty implementation. Does nothing.
         * @param l the listener to add
         */
        @Override
        public void addChangeListener (ChangeListener l) {
            // PENDING up to now listen on separate managers
        }

        /** Implements {@code UndoRedo}. Empty implementation. Does nothing.
         * @param l the listener to remove
         * @see #addChangeListener
         */
        @Override
        public void removeChangeListener (ChangeListener l) {
            // PENDING
        }

        /** Implements {@code UndoRedo}. Get a human-presentable name describing the
         * undo operation.
         * @return the name
         */
        @Override
        public synchronized String getUndoPresentationName () {
            UndoRedo chosenManager = getNextUndo();

            if (chosenManager == null) {
                return "Undo"; // NOI18N // AbstractUndoableEdit.UndoName is not accessible
            } else {
                return chosenManager.getUndoPresentationName();
            }
        }

        /** Implements {@code UndoRedo}. Get a human-presentable name describing the
         * redo operation.
         * @return the name
         */
        @Override
        public synchronized String getRedoPresentationName () {
            UndoRedo chosenManager = getNextRedo();
            if (chosenManager == null) {
                return "Redo"; // NOI18N // AbstractUndoableEdit.RedoName is not accessible
            } else {
                return chosenManager.getRedoPresentationName();
            }
        }
        
    } // End of nested class CompoundUndoRedoManager.
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy