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.
 */
/*
 * UndoManager.java
 *
 * Created on 23. leden 2004, 16:24
 */

package org.netbeans.modules.javacore.internalapi;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.*;
import javax.swing.event.*;
import javax.swing.text.Document;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.classpath.GlobalPathRegistryEvent;
import org.netbeans.api.java.classpath.GlobalPathRegistryListener;
import org.netbeans.jmi.javamodel.JavaModelPackage;
import org.netbeans.jmi.javamodel.Resource;
import org.netbeans.modules.javacore.ExclusiveMutex;
import org.netbeans.modules.javacore.JMManager;
import org.netbeans.modules.javacore.RepositoryUpdater;
import org.netbeans.modules.javacore.Util;
import org.netbeans.modules.javacore.jmiimpl.javamodel.ResourceImpl;
import org.openide.LifecycleManager;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.*;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.text.CloneableEditorSupport;

/**
 *
 * @author  Jan Becicka
 */
public class UndoManager extends FileChangeAdapter implements DocumentListener, RepositoryListener, ChangeListener, GlobalPathRegistryListener {
    
    /** stack of undo items */
    private LinkedList undoList;
    
    /** stack of redo items */
    private LinkedList redoList;

    /** set of all CloneableEditorSupports */
    private final HashSet allCES = new HashSet();
    
    /** map document -> CloneableEditorSupport */
    private final HashMap documentToCES = new HashMap();
    
    /** map document -> CloneableEditorSupport */ 
    private final HashMap listenerToCES = new HashMap();
    private boolean listenersRegistered = false;
    
    public static final String PROP_STATE = "state"; //NOI18N
    
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    
    private boolean wasUndo = false;
    private boolean wasRedo = false;
    private boolean transactionStart;
    private boolean dontDeleteUndo = false;
    
    private IdentityHashMap descriptionMap;
    private String description;
    private HashSet modifiedResources;
    private JMManager manager;
    private ProgressListener progress;


    /** Creates a new instance of UndoManager */
    public UndoManager() {
        undoList = new LinkedList();
        redoList = new LinkedList();
        descriptionMap = new IdentityHashMap();
        modifiedResources = new HashSet();
        manager = (JMManager) JMManager.getManager();
    }
    
    public UndoManager(ProgressListener progress) {
        this();
        this.progress = progress;
    }
    
    public void setUndoDescription(String desc) { 
        description = desc;
    }
    
    public String getUndoDescription() {
        if (undoList.isEmpty()) return null;
        return (String) descriptionMap.get(undoList.getFirst());
    }
    
    public String getRedoDescription() { 
        if (redoList.isEmpty()) return null;
        return (String) descriptionMap.get(redoList.getFirst());
    }
    
    /** called to mark transaction start
     */
    public void transactionStarted() {
        modifiedResources.clear();
        transactionStart = true;
        unregisterListeners();
        RepositoryUpdater.getDefault().setListenOnChanges(false);
    }
    
    /**
     * called to mark end of transaction
     */
    public void transactionEnded(boolean fail) {
        try {
            description = null;
            parseModified();
            if (fail && !undoList.isEmpty())
                undoList.removeFirst();
            else {
                // [TODO] (jb) this code disables undos for changes using org.openide.src
                if (isUndoAvailable() && getUndoDescription() == null) {
                    descriptionMap.remove(undoList.removeFirst());
                    clearIfPossible();
                }
                
            }
            
            dontDeleteUndo = true;
            invalidate(null);
            dontDeleteUndo = false;
        } finally {
            RepositoryUpdater.getDefault().setListenOnChanges(true);
            registerListeners();
        }
        fireStateChange();
    }
    
    /** undo last transaction */
    public void undo() {
        //System.out.println("************* Starting UNDO");
        if (isUndoAvailable()) {
            JavaMetamodel.getDefaultRepository().beginTrans(true);
            transactionStarted();
            boolean fail = true;
            try {
                wasUndo = true;
                LinkedList undo =  (LinkedList) undoList.getFirst();
                fireProgressListenerStart(0, undo.size());
                undoList.removeFirst();
                Iterator undoIterator = undo.iterator();
                UndoItem item;
                redoList.addFirst(new LinkedList());
                descriptionMap.put(redoList.getFirst(), descriptionMap.remove(undo));
                while (undoIterator.hasNext()) {
                    fireProgressListenerStep();
                    item = (UndoItem) undoIterator.next();
                    item.undo();
                    if (item instanceof ExternalUndoItem) {
                        addItem(item);
                    } else {
                        //item is ResourceUndoItem
                        modifiedResources.add(manager.getDataObject(((UndoManager.ResourceUndoItem)item).getResource()));
                    }
                }
                fail = false;
            } catch (RuntimeException e) {
                e.printStackTrace();
            } finally {
                wasUndo = false;
                JavaMetamodel.getDefaultRepository().endTrans(fail);
                transactionEnded(fail);
                fireProgressListenerStop();
                fireStateChange();
            }
        }
    }
    
    /** redo last undo
     */
    public void redo() {
        //System.out.println("************* Starting REDO");
        if (isRedoAvailable()) {
            JavaMetamodel.getDefaultRepository().beginTrans(true);
            boolean fail = true;
            transactionStarted();
            try {
                wasRedo = true;
                LinkedList redo =  (LinkedList) redoList.getFirst();
                fireProgressListenerStart(1, redo.size());
                redoList.removeFirst();
                Iterator redoIterator = redo.iterator();
                UndoItem item;
                description = (String) descriptionMap.remove(redo);
                while (redoIterator.hasNext()) {
                    fireProgressListenerStep();
                    item = (UndoItem) redoIterator.next();
                    item.redo();
                    if (item instanceof ExternalUndoItem) {
                        addItem(item);
                    } else {
                        //item is ResourceUndoItem
                        modifiedResources.add(manager.getDataObject(((UndoManager.ResourceUndoItem)item).getResource()));
                    }
                }
                fail = false;
            } finally {
                wasRedo = false;
                JavaMetamodel.getDefaultRepository().endTrans(fail);
                transactionEnded(fail);
                fireProgressListenerStop();
                fireStateChange();
            }
        }
    }
    
    /** clean undo/redo stacks */
    public void clear() {
        undoList.clear();
        redoList.clear();
        descriptionMap.clear();
        fireStateChange();
    }
    
    /** add new item to undo/redo list */
    public void addItem(Resource r, List l) {
        addItem(new ResourceUndoItem(r,l));
    }
    
    /** add new item to undo/redo list */
    public void addItem(ExternalChange change) {
        addItem(new ExternalUndoItem(change));
    }
    
    /** add new item to undo/redo list */
    public void addItem(UndoItem item) {
        if (wasUndo) {
            LinkedList redo = (LinkedList) this.redoList.getFirst();
            redo.addFirst(item);
        } else {
            if (transactionStart) {
                undoList.addFirst(new LinkedList());
                descriptionMap.put(undoList.getFirst(), description);
                transactionStart = false;
            }
            LinkedList undo = (LinkedList) this.undoList.getFirst();
            undo.addFirst(item);
        }
        if (! (wasUndo || wasRedo)) 
            redoList.clear();
    }
     
    public boolean isUndoAvailable() {
        return !undoList.isEmpty();
    }
    
    public boolean isRedoAvailable() {
        return !redoList.isEmpty();
    }
    
    public void addPropertyChangeListener(PropertyChangeListener pcl) {
        pcs.addPropertyChangeListener(pcl);
    }
    
    public void removePropertyChangeListener(PropertyChangeListener pcl) {
        pcs.removePropertyChangeListener(pcl);
    }

    private void fireStateChange() {
        pcs.firePropertyChange(PROP_STATE, null, null);
    }
    
    public void watch(Collection ceSupports, InvalidationListener l) {
        synchronized (allCES) {
            registerListeners();
            for (Iterator it = ceSupports.iterator(); it.hasNext();) {
                CloneableEditorSupport ces = (CloneableEditorSupport) it.next();
                if (allCES.add(ces)) {
                    ces.addChangeListener(this);
                    Document d = ces.getDocument();
                    if (d != null) {
                        d.addDocumentListener(this);
                        documentToCES.put(d, ces);
                    }
                }
            }
            if (l != null) {
                listenerToCES.put(l, ceSupports);
            }
        }
    }
    
    public void stopWatching(InvalidationListener l) {
        //synchronized (undoStack) {
            synchronized (allCES) {
                listenerToCES.remove(l);
                clearIfPossible();
            }
        //}
    }
    
    public void pathsAdded(GlobalPathRegistryEvent event) {
    }

    public void pathsRemoved(GlobalPathRegistryEvent event) {
        assert event != null : "event == null"; // NOI18N
        if (event.getId().equals(ClassPath.SOURCE)) {
            clear();
        }
    }

    private void registerListeners() {
        if (listenersRegistered) return;
        GlobalPathRegistry.getDefault().addGlobalPathRegistryListener(this);
        Util.addFileSystemsListener(this);
        Repository.getDefault().addRepositoryListener(this);
        for (Iterator it = allCES.iterator(); it.hasNext();) {
            ((CloneableEditorSupport) it.next()).addChangeListener(this);
        }
        for (Iterator it = documentToCES.keySet().iterator(); it.hasNext();) {
            ((Document) it.next()).addDocumentListener(this);
        }
        listenersRegistered = true;
    }
    
    private void unregisterListeners() {
        if (!listenersRegistered) return;
        Util.removeFileSystemsListener(this);
        GlobalPathRegistry.getDefault().removeGlobalPathRegistryListener(this);
        Repository.getDefault().removeRepositoryListener(this);
        for (Iterator it = allCES.iterator(); it.hasNext();) {
            ((CloneableEditorSupport) it.next()).removeChangeListener(this);
        }
        for (Iterator it = documentToCES.keySet().iterator(); it.hasNext();) {
            ((Document) it.next()).removeDocumentListener(this);
        }
        listenersRegistered = false;
    }
    
    private void invalidate(CloneableEditorSupport ces) {
        synchronized (undoList) {
            if (!(wasRedo || wasUndo) && !dontDeleteUndo) {
                clear();
            } 
            synchronized (allCES) {
                if (ces == null) {
                    // invalidate all
                    for (Iterator it = listenerToCES.keySet().iterator(); it.hasNext();) {
                        ((InvalidationListener) it.next()).invalidateObject();
                    }
                    listenerToCES.clear();
                } else {
                    for (Iterator it = listenerToCES.entrySet().iterator(); it.hasNext();) {
                        Map.Entry e = (Map.Entry) it.next();
                        if (((HashSet) e.getValue()).contains(ces)) {
                            ((InvalidationListener) e.getKey()).invalidateObject();
                            it.remove();
                        }
                    }
                    /*ces.removeChangeListener(this);
                    allCES.remove(ces);
                    Document d = ces.getDocument();
                    if (d != null) {
                        d.removeDocumentListener(this);
                        documentToCES.remove(d);
                    }
                     */
                }
                clearIfPossible();
            }
        }
    }
    
    private void clearIfPossible() {
        if (listenerToCES.isEmpty() && undoList.isEmpty() && redoList.isEmpty()) {
            //unregisterListeners();
            allCES.clear();
            documentToCES.clear();
        }
    }        
    
    // FileChangeAdapter ........................................................
    
    public void fileChanged(FileEvent fe) {   
        FileObject file = fe.getFile();
        if (!Util.isJavaFile(file)) {
            return;
        }
        if (file != null) {
            DataObject obj;
            try {
                obj = DataObject.find(file);
            } catch (DataObjectNotFoundException e) {
                return;
            }
            EditorCookie ec = (EditorCookie) obj.getCookie(EditorCookie.class);
            if (ec != null) {
                CloneableEditorSupport ces = (CloneableEditorSupport) documentToCES.get(ec.getDocument());
                if (ces != null) {
                    invalidate(ces);
                }
            }
        }
    }
    
    public void fileDeleted(FileEvent fe) {
        if (Util.isJavaFile(fe.getFile())) { // NOI18N
            invalidate(null);
        }
        //fileChanged(fe);
    }

    public void fileRenamed(FileRenameEvent fe) {
        fileChanged(fe);
    }
    
    // DocumentListener .........................................................
    
    public void changedUpdate(DocumentEvent e) {
    }

    public void insertUpdate(DocumentEvent e) {        
        invalidate((CloneableEditorSupport) documentToCES.get(e.getDocument()));
    }

    public void removeUpdate(DocumentEvent e) {
        invalidate((CloneableEditorSupport) documentToCES.get(e.getDocument()));
    }
        
    // RepositoryListener .......................................................
    
    public void fileSystemAdded(RepositoryEvent ev) {
    }
    
    public void fileSystemPoolReordered(RepositoryReorderedEvent ev) {
    }
    
    public void fileSystemRemoved(RepositoryEvent ev) {
        invalidate(null);
    }
    
    public void stateChanged(ChangeEvent e) {
        synchronized (allCES) {
            CloneableEditorSupport ces = (CloneableEditorSupport) e.getSource();
            Document d = ces.getDocument();
            for (Iterator it = documentToCES.entrySet().iterator(); it.hasNext();) {
                Map.Entry en = (Map.Entry) it.next();
                if (en.getValue() == ces) {
                    ((Document) en.getKey()).removeDocumentListener(this);
                    it.remove();
                    break;
                }
            }
            if (d != null) {
                documentToCES.put(d, ces);
                d.addDocumentListener(this);
            }
        }
    }
    
    public void saveAll() {
        synchronized (allCES) {
            unregisterListeners();
            try {
                LifecycleManager.getDefault().saveAll();
            } finally {
                registerListeners();
            }
        }
    }

    private void parseModified() {
        ExclusiveMutex mutex = manager.getTransactionMutex();

        for (Iterator i = modifiedResources.iterator(); i.hasNext();) {
            mutex.addModified((DataObject) i.next());
        }
        manager.getDefaultRepository().beginTrans(true);
        manager.getDefaultRepository().endTrans();
        
        //resources must be parsed twice to avoid #48913
        for (Iterator i = modifiedResources.iterator(); i.hasNext();) {
            mutex.addModified((DataObject) i.next());
        }
        modifiedResources.clear();
        manager.getDefaultRepository().beginTrans(true);
        manager.getDefaultRepository().endTrans();
        
    }
    
    private void fireProgressListenerStart(int type, int count) {
        stepCounter = 0;
        if (progress == null)
            return;
        progress.start(new ProgressEvent(this, ProgressEvent.START, type, count));
    }
    
    private int stepCounter = 0;
    /** Notifies all registered listeners about the event.
     */
    private void fireProgressListenerStep() {
        if (progress == null)
            return;
        progress.step(new ProgressEvent(this, ProgressEvent.STEP, 0, ++stepCounter));
    }

    /** Notifies all registered listeners about the event.
     */
    private void fireProgressListenerStop() {
        if (progress == null)
            return;
        progress.stop(new ProgressEvent(this, ProgressEvent.STOP));
    }
    
    private interface UndoItem {
        void undo();
        void redo();
    }
    
    private final class ResourceUndoItem implements UndoItem {
        private JavaModelPackage model;
        private String resourceName = null;
        private List diffList;
        
        public ResourceUndoItem(Resource r, List l) {
            model = (JavaModelPackage) r.refImmediatePackage();
            resourceName = r.getName();
            diffList = l;
        }
        
        /**
         * Getter for property diffList.
         * @return Value of property diffList.
         */
        public List getDiffList() {
            return diffList;
        }
        
        /**
         * Setter for property diffList.
         * @param diffList New value of property diffList.
         */
        public void setDiffList(List diffList) {
            this.diffList = diffList;
        }
        
        /**
         * Getter for property resource.
         * @return Value of property resource.
         */
        public Resource getResource() {
            return model.getResource().resolveResource(resourceName, false);
        }
        
        /**
         * Setter for property resource.
         * @param resource New value of property resource.
         */
        public void setResource(Resource resource) {
            model = (JavaModelPackage) resource.refImmediatePackage();
            resourceName = resource.getName();
        }
        
        public void undo() {
            applyDiff();
        }
        
        private void applyDiff() {
            ((ResourceImpl) getResource()).applyDiff(diffList, false, false);
        }
        
        public void redo() {
            applyDiff(); 
        }
    }
    
    private final class ExternalUndoItem implements UndoItem {
        
        private ExternalChange change;
        
        public ExternalUndoItem (ExternalChange change) {
            this.change = change;
        }
        
        public void undo() {
            change.undoExternalChange();
        }
        
        public void redo() {
            change.performExternalChange();
        }
    }
    
}    
... 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.