com.mentor.nucleus.bp.core.common.TransactionManager.java Source code

Java tutorial

Introduction

Here is the source code for com.mentor.nucleus.bp.core.common.TransactionManager.java

Source

//=====================================================================
//
//File:      $RCSfile: TransactionManager.java,v $
//Version:   $Revision: 1.43 $
//Modified:  $Date: 2013/06/12 13:08:25 $
//
//(c) Copyright 2005-2014 by Mentor Graphics Corp. All rights reserved.
//
//=====================================================================
// Licensed 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 com.mentor.nucleus.bp.core.common;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;

import com.mentor.nucleus.bp.core.CorePlugin;
import com.mentor.nucleus.bp.core.Modeleventnotification_c;
import com.mentor.nucleus.bp.core.Ooaofooa;
import com.mentor.nucleus.bp.core.PropertyViewListener;
import com.mentor.nucleus.bp.core.SystemModel_c;
import com.mentor.nucleus.bp.core.ui.Selection;
import com.mentor.nucleus.bp.core.ui.marker.DelayedMarkerJob;
import com.mentor.nucleus.bp.core.util.CoreUtil;
import com.mentor.nucleus.bp.core.util.OoaofgraphicsUtil;
import com.mentor.nucleus.bp.core.util.TransactionUtil;
import com.mentor.nucleus.bp.core.util.UIUtil;

/**
 * Goal 1: batching of deltas Goal 2: providing locking mechanism
 */
public class TransactionManager {
    Transaction activeTransaction;

    int maxStackSize = 87;

    ArrayList<Transaction> undoStack = new ArrayList<Transaction>();
    ArrayList<Transaction> redoStack = new ArrayList<Transaction>();

    static ArrayList<IFile> affectedComponents = new ArrayList<IFile>();

    ListenerList transactionListeners = new ListenerList();
    private Action redoAction;
    private Action undoAction;

    static ArrayList<String> affectedModelElementsNames = new ArrayList<String>();
    private Transaction lastTransaction;

    private boolean ignoreResourceChanges = false;

    private Job transactionEndedJob;

    public boolean ignoreResourceChangesMarker;

    private ListenerList transactionListenersAtEnd = new ListenerList();

    public static final String FAMILY_TRANSACTION = "Transaction Jobs"; //$NON-NLS-1$
    public static final int FORWARD_DIRECTION = 1;
    public static final int REVERSE_DIRECTION = 2;

    private static ConsistencyTransactionListener consistencyListener = null;

    private static TransactionManager singleton;

    public TransactionManager() {
        // add a listener for changes to model persistence files,
        // since such changes might warrant the clearing of this manager's
        // undo/redo stacks
        CorePlugin.getWorkspace().addResourceChangeListener(new ModelFileChangeListener());
    }

    public ArrayList<Transaction> getUndoStack() {
        return undoStack;
    }

    public Transaction getActiveTransaction() {
        return activeTransaction;
    }

    public Transaction startTransaction(String displayName, ModelElement[] modelElements)
            throws TransactionException {
        SortedCompoundDeltaCollector compoundDeltaCollector = new SortedCompoundDeltaCollector(modelElements);
        return startTransaction(displayName, compoundDeltaCollector, Transaction.DEFAULT_TYPE);
    }

    public Transaction startTransaction(String displayName, ModelElement[] modelElements, boolean undoable)
            throws TransactionException {
        SortedCompoundDeltaCollector compoundDeltaCollector = new SortedCompoundDeltaCollector(modelElements);
        return startTransaction(displayName, compoundDeltaCollector, Transaction.DEFAULT_TYPE, undoable);
    }

    public Transaction startTransaction(String displayName, ModelRoot modelRoot, boolean undoable, String type)
            throws TransactionException {
        return startTransaction(displayName, modelRoot.getDeltaCollector(), type, undoable);
    }

    public Transaction startTransaction(String displayName, ModelElement[] modelElements, String type)
            throws TransactionException {
        SortedCompoundDeltaCollector compoundDeltaCollector = new SortedCompoundDeltaCollector(modelElements);
        return startTransaction(displayName, compoundDeltaCollector, type);
    }

    public Transaction startTransaction(String displayName, ModelElement[] modelElements, String type,
            boolean undoable) throws TransactionException {
        SortedCompoundDeltaCollector compoundDeltaCollector = new SortedCompoundDeltaCollector(modelElements);
        return startTransaction(displayName, compoundDeltaCollector, type, undoable);
    }

    public Transaction startTransaction(String name, SortedCompoundDeltaCollector collector)
            throws TransactionException {
        return startTransaction(name, collector, Transaction.DEFAULT_TYPE);
    }

    public Transaction startTransaction(String displayName, ModelRoot modelRoot) throws TransactionException {
        return startTransaction(displayName, modelRoot.getDeltaCollector(), Transaction.DEFAULT_TYPE);
    }

    public Transaction startTransaction(String displayName, ModelRoot modelRoot, boolean undoable)
            throws TransactionException {
        return startTransaction(displayName, modelRoot.getDeltaCollector(), Transaction.DEFAULT_TYPE, undoable);
    }

    public Transaction startTransaction(String displayName, IDeltaCollector deltaCollector, String type)
            throws TransactionException {
        return startTransaction(displayName, deltaCollector, type, true);
    }

    public Transaction startTransaction(String displayName, IDeltaCollector deltaCollector, String type,
            boolean undoable) throws TransactionException {
        if (activeTransaction != null) {
            throw new TransactionException(
                    "Transaction '" + activeTransaction.getDisplayName() + "' is already in progress");
        }

        if (consistencyListener == null) {
            consistencyListener = new ConsistencyTransactionListener();
            addTransactionListener(consistencyListener);
        }
        activeTransaction = new Transaction(displayName, deltaCollector, type, undoable, this);

        ModelRoot[] roots = activeTransaction.getParticipatingModelRoots();
        for (int i = 0; i < roots.length; i++) {
            if (!roots[i].getId().equals(Ooaofooa.DEFAULT_WORKING_MODELSPACE)) {
                CorePlugin.logError(
                        "Deltas from a Transaction started on model-roots other than the default root will be ignored.",
                        null);
            }
        }

        deltaCollector.startCollecting(activeTransaction);

        fireTransactionStarted(activeTransaction);

        return activeTransaction;
    }

    public void cancelTransaction(Transaction transaction) {
        cancelTransaction(transaction, null);
    }

    public void cancelTransaction(Transaction transaction, Exception exception) {
        cancelTransaction(transaction, exception, true);
    }

    public void cancelTransaction(Transaction transaction, Exception exception, boolean revert) {
        if (activeTransaction == null || activeTransaction != transaction) {
            throw new IllegalStateException("no transaction is in progress");
        }
        try {
            IDeltaCollector deltaCollector = activeTransaction.getDeltaCollector();
            deltaCollector.endCollecting();
        } finally {
            if (revert) {
                activeTransaction.revert(true);
            }
            // if an exception was passed show a dialog to the user
            if (exception != null) {
                UIUtil.openError(null, null, "Unable to complete transaction.  The transaction was canceled.");
            }
            activeTransaction = null;
        }

        fireTransactionCancelled(transaction);
        fireBatchedModelChangesFor(transaction);
    }

    /**
     * Method used to get the last stored transaction for the given type.
     * 
     */
    private Transaction getTransactionToRevert(String transactionType) {
        if (transactionType.equals(Transaction.UNDO_TYPE)) {
            if (undoStack.size() == 0) {
                return null;
            }
            return (Transaction) undoStack.get(undoStack.size() - 1);
        } else if (transactionType.equals(Transaction.REDO_TYPE)) {
            if (redoStack.size() == 0) {
                return null;
            }
            return (Transaction) redoStack.get(redoStack.size() - 1);
        } else {
            return null;
        }
    }

    private void addTransactionToStack(ArrayList<Transaction> stack, Transaction transaction) {
        if (stack.size() == maxStackSize) {
            // if this stack is at its limit
            // remove the bottom transaction
            stack.remove(0);
        }
        stack.add(transaction);
    }

    /**
     * Method used by undo and redo actions
     * 
     * @param transactionType
     */
    public void revertLastTransaction(String transactionType) {
        Transaction transaction = getTransactionToRevert(transactionType);
        revertTransaction(transaction, false);
    }

    private void revertTransaction(Transaction transaction, boolean inMemoryOnly) {
        revertTransaction(transaction, inMemoryOnly, true, null);
    }

    public Transaction revertTransaction(Transaction transaction, boolean inMemoryOnly, boolean undoable,
            IProgressMonitor monitor) {
        ModelRoot[] modelRoots = transaction.getParticipatingModelRoots();
        String type = "";
        if (transaction.getType().equals(Transaction.UNDO_TYPE)) {
            type = Transaction.REDO_TYPE;
        } else {
            type = Transaction.UNDO_TYPE;
        }
        Transaction revertTransaction = null;
        try {
            revertTransaction = startTransaction("Transaction: Revert transaction", modelRoots, type, undoable);
            revertTransaction.memoryOnly = inMemoryOnly;
        } catch (TransactionException e) {
            CorePlugin.logError("Unable to start revert transaction.", e);
        }
        if (monitor == null)
            transaction.revert(inMemoryOnly);
        else
            transaction.revert(monitor, inMemoryOnly);
        endTransaction(revertTransaction);
        return revertTransaction;
    }

    public boolean disableDialog = false;

    /**
     * This method checks whether or not a transaction can complete. It checks
     * each affected component to see if any are read-only, if it finds any that
     * are it shows a dialog to the user letting them make the choice of whether
     * or not to try and force the transaction or cancel it.
     * 
     * @param transaction
     * @return
     */
    private boolean checkIfTransactionCanComplete(Transaction transaction) {

        if (!affectedModelElementsNames.isEmpty() && !disableDialog) {
            String message = "";
            String str[] = (String[]) affectedModelElementsNames.toArray(new String[0]);
            for (int i = 0; i < str.length; i++) {
                message = message + str[i] + "\n";
            }
            affectedModelElementsNames.clear();
            boolean result = UIUtil.openScrollableTextDialog(
                    PlatformUI.getWorkbench().getDisplay().getActiveShell(), true, "Confirm Changes", message,
                    "The following elements will have their type reset to the default type  as a result of this operation."
                            + "\nWould you like to proceed ?",
                    null, BridgePointPreferencesStore.SHOW_SYNC_DELETION_DIALOG, true);
            if (!result) {
                return false;
            }
        } else {
            // need to clear the list of affected model elements so that
            // the next transaction does not report anything
            affectedModelElementsNames.clear();
        }

        /*
         * see if the files to be affected are read-only, if so let the user
         * decide whether or not the transaction proceeds
         */

        // clear out old read-only components
        affectedComponents.clear();

        PersistableModelComponent component = null;
        NonRootModelElement modelElement = null;
        ModelRoot[] modelRoots = transaction.getParticipatingModelRoots();
        for (int i = 0; i < modelRoots.length; i++) {
            IModelDelta[] deltas = transaction.getDeltas(modelRoots[i]);
            for (int j = 0; j < deltas.length; j++) {
                IModelDelta delta = deltas[j];
                modelElement = (NonRootModelElement) delta.getModelElement();
                if (modelElement == null) {
                    continue;
                }
                component = modelElement.getPersistableComponent(true);
                Ooaofooa ooaroot = null;
                if (modelRoots[i] instanceof Ooaofooa) {
                    ooaroot = (Ooaofooa) modelRoots[i];
                }
                if (component != null) {
                    // if the delta is a deletion on a root element
                    // do not check as it has already been checked
                    // by TransactionUtil.modifySelectedResources()
                    // also remove it from the affectedComponents if
                    // a child has added it
                    if (!(PersistenceManager.getHierarchyMetaData().isComponentRoot(modelElement)
                            && deltas[j].getKind() == Modeleventnotification_c.DELTA_DELETE)) {
                        // if this change is an attribute change and
                        // is a result of the setRepresents call, ignore
                        // it
                        if (delta instanceof AttributeChangeModelDelta) {
                            AttributeChangeModelDelta changeDelta = (AttributeChangeModelDelta) delta;
                            if (changeDelta.getAttributeName().equals("Represents")) {
                                if (!(modelElement.getModelRoot() instanceof Ooaofooa)) {
                                    continue;
                                }
                            }
                        }
                        if (component != null) {
                            if (!affectedComponents.contains(component.getFile()))
                                affectedComponents.add(component.getFile());
                        }
                    } else {
                        if (affectedComponents.contains(component.getFile())) {
                            affectedComponents.remove(component.getFile());
                        }
                    }
                    if (delta instanceof AttributeChangeModelDelta) {
                        AttributeChangeModelDelta modelDelta = (AttributeChangeModelDelta) delta;
                        if (modelDelta.isPersistenceRelatedChange()) {
                            if ("Name".equals(modelDelta.getAttributeName()) //$NON-NLS-1$
                                    && component.getRootModelElement() == modelDelta.getModelElement()) {

                                // for all renames we must check external
                                // rgos, and children components if this
                                // element
                                // is a component root
                                if (PersistenceManager.getHierarchyMetaData().isComponentRoot(modelElement)) {
                                    List<PersistableModelComponent> children = gatherChildrenComponents(component);
                                    for (PersistableModelComponent child : children) {
                                        if (!affectedComponents.contains(child.getFile())) {
                                            affectedComponents.add(child.getFile());
                                        }

                                    }
                                }

                                // now look for rgos
                                List<?> selfExternalRGOs = PersistenceManager.getHierarchyMetaData()
                                        .findExternalRGOsToContainingComponent(component.getRootModelElement(),
                                                true);
                                for (Object rgo : selfExternalRGOs) {
                                    PersistableModelComponent target = ((NonRootModelElement) rgo)
                                            .getPersistableComponent();
                                    if (target != null && !affectedComponents.contains(target.getFile())) {
                                        affectedComponents.add(target.getFile());
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        if (affectedComponents.size() == 0) {
            return true;
        }
        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        Object context = IWorkspace.VALIDATE_PROMPT;
        try {
            context = PlatformUI.getWorkbench().getDisplay().getActiveShell();
        } catch (SWTException exception) {
            // An SWTException here means that this is not being called from the
            // same thread as the receiver.  In this case there is no shell
            // to display messages to the user in.  In this case, we use 
            // IWorkspace.VALIDATE_PROMPT which will still prompt the user as 
            // needed for change operations.
        }

        IStatus status = workspace.validateEdit(affectedComponents.toArray(new IFile[0]), context);
        // below we must check to see if any files are still
        // readonly as certain team providers return a status
        // of OK when the preference is to do nothing when
        // version controlled files are modified that are not
        // checked out, we do not want to allow modification
        // in this case as the team providers will not actually
        // take the change. (Culprit team provider: ClearCase)
        if (status.isOK()) {
            return TransactionUtil.verifyValidateEdit(affectedComponents);
        } else {
            return false;
        }
    }

    private List<PersistableModelComponent> gatherChildrenComponents(PersistableModelComponent component) {
        List<PersistableModelComponent> collection = new ArrayList<PersistableModelComponent>();
        Collection<?> children = component.getChildren();
        // now persist any other proxy data that is out there
        // referring to this component
        for (Iterator<?> iter = children.iterator(); iter.hasNext();) {
            PersistableModelComponent child = (PersistableModelComponent) iter.next();
            collection.add(child);
            collection.addAll(gatherChildrenComponents(child));
        }
        return collection;
    }

    public boolean endTransaction(Transaction transaction) {
        return endTransaction(transaction, true);
    }

    public void setActiveTransaction(Transaction transaction) {
        activeTransaction = transaction;
    }

    public boolean endTransaction(Transaction transaction, boolean lockWorkspace) {
        if (activeTransaction == null || activeTransaction != transaction) {
            throw new IllegalStateException("no transaction is in progress");
        }

        try {
            IDeltaCollector deltaCollector = activeTransaction.getDeltaCollector();
            deltaCollector.endCollecting();
        } finally {
            if (!transaction.memoryOnly) {
                lastTransaction = activeTransaction;
            }
            activeTransaction = null;
        }
        return completeTransaction(transaction, lockWorkspace);
    }

    private boolean completeTransaction(Transaction transaction, boolean lockWorkspace) {
        // if persistence is enabled tell resource
        // listeners to ignore the persist calls
        // that are to come
        if (Ooaofooa.getDefaultInstance().persistEnabled()) {
            ignoreResourceChangesMarker = true;
            ComponentResourceListener.setIgnoreResourceChangesMarker(true);
        }
        boolean completeTransaction = true;
        // check all transactions that are done
        // in both memory and on disk
        if (!transaction.memoryOnly) {
            completeTransaction = checkIfTransactionCanComplete(transaction);
        } else {
            completeTransaction = false;
        }
        // do not add transactions that are completed in
        // memory only, or transactions that are specified
        // as not to be undoable
        if (completeTransaction) {
            // add all transactions to the undo stack
            // except for any undo transactions
            if (!transaction.getType().equals(Transaction.UNDO_TYPE) && transaction.isUndoable()) {
                addTransactionToStack(undoStack, transaction);
                if (!transaction.getType().equals(Transaction.REDO_TYPE)) {
                    // if this transaction is other than redo or undo
                    // clear the redo stack
                    redoStack.clear();
                }
            }

            // if this transaction is of the type undo
            // move it to the redo stack
            if (transaction.getType().equals(Transaction.UNDO_TYPE)) {
                undoStack.remove(undoStack.size() - 1);
                addTransactionToStack(redoStack, transaction);
            } else if (transaction.getType().equals(Transaction.REDO_TYPE)) {
                redoStack.remove(redoStack.size() - 1);
            }

            // now enable or disable the undo/redo actions
            setUndoRedoActionsState();
        } else {
            // remove the lastTransaction from either the undo or
            // redo stack depending on where it was placed
            if (undoStack.contains(lastTransaction)) {
                undoStack.remove(lastTransaction);
            } else if (redoStack.contains(lastTransaction)) {
                redoStack.remove(lastTransaction);
            }
            // set the last transaction to null as it was not completed
            lastTransaction = null;
        }

        // if transaction was unable to complete
        // there is no need to notify any transaction
        // listeners as the changes will be reverted
        // in memory and the files on the disk will
        // remain consistent
        if (completeTransaction) {
            if (lockWorkspace) {
                if (transactionEndedJob == null) {
                    transactionEndedJob = new TransactionEndedJob("Transaction Ended");
                    transactionEndedJob.setSystem(true);
                    transactionEndedJob.setPriority(Job.INTERACTIVE);
                }
                // schedule the job at the next possible time
                transactionEndedJob.schedule();
                try {
                    transactionEndedJob.join();
                } catch (InterruptedException e) {
                    CorePlugin.logError("Transaction job was interrupted unexpectedly", e);
                } finally {
                    if (Ooaofooa.getDefaultInstance().persistEnabled()) {
                        ignoreResourceChangesMarker = false;
                        ComponentResourceListener.setIgnoreResourceChangesMarker(false);
                    }
                }
            } else {
                fireTransactionEndedEvents();
            }
        } else {
            transaction.revert();
            fireTransactionCancelled(transaction);
            return true;
        }
        return false;
    }

    public void removeTransactionFromStack(Transaction transaction, String type) {
        if (type.equals(Transaction.UNDO_TYPE)) {
            undoStack.remove(transaction);
        } else {
            redoStack.remove(transaction);
        }
    }

    private class TransactionEndedJob extends Job {
        String jobFamily = FAMILY_TRANSACTION;

        public boolean belongsTo(Object family) {
            return jobFamily.equals(family);
        }

        public TransactionEndedJob(String name) {
            super(name);
        }

        public IStatus run(IProgressMonitor monitor) {
            try {
                IWorkspace workspace = ResourcesPlugin.getWorkspace();
                workspace.run(new IWorkspaceRunnable() {
                    public void run(IProgressMonitor monitor) throws CoreException {
                        fireTransactionEndedEvents();
                    }
                }, null);
            } catch (CoreException e) {
                CorePlugin.logError(
                        "Unable to handle modifications made by the transaction: " + lastTransaction.displayName,
                        e);
            }
            return Status.OK_STATUS;
        }

    }

    protected void fireTransactionEndedEvents() {
        fireTransactionEnded(lastTransaction);
        // By firing model changes we are ensuring that batched
        // model
        // changes
        // will always be delivered after transaction listeners,
        // otherwise
        // it
        // was possible that model changes could be delivered
        // either before
        // or
        // after transaction listeners.
        fireBatchedModelChangesFor(lastTransaction);
        // there is currently one listener that must be the last
        // thing ran, it is registered in a different list then
        // the normal transaction listeners
        fireLastTransactionEnded(lastTransaction);
    }

    private void fireBatchedModelChangesFor(Transaction transaction) {
        ModelRoot[] modelRoots = transaction.getParticipatingModelRoots();
        for (int i = 0; i < modelRoots.length; i++) {
            modelRoots[i].fireBatchedModelChanges(transaction.getDeltas(modelRoots[i]));
        }
    }

    public void addTransactionListener(ITransactionListener listener) {
        addTransactionListener(listener, false);
    }

    public void addTransactionListener(ITransactionListener listener, boolean last) {
        if (last) {
            transactionListenersAtEnd.add(listener);
        } else {
            transactionListeners.add(listener);
        }
    }

    public void removeTransactionListener(ITransactionListener listener) {
        transactionListeners.remove(listener);
        transactionListenersAtEnd.remove(listener);
    }

    protected void fireTransactionStarted(Transaction transaction) {
        Object[] listeners = transactionListeners.getListeners();
        for (int i = 0; i < listeners.length; ++i) {
            final ITransactionListener l = (ITransactionListener) listeners[i];
            l.transactionStarted(transaction);
        }
        Object[] lastListeners = transactionListenersAtEnd.getListeners();
        for (int i = 0; i < lastListeners.length; ++i) {
            final ITransactionListener l = (ITransactionListener) lastListeners[i];
            l.transactionStarted(transaction);
        }
    }

    protected void fireTransactionEnded(Transaction transaction) {
        Object[] listeners = transactionListeners.getListeners();
        for (int i = 0; i < listeners.length; ++i) {
            final ITransactionListener l = (ITransactionListener) listeners[i];
            l.transactionEnded(transaction);
        }
    }

    protected void fireLastTransactionEnded(Transaction transaction) {
        Object[] lastListeners = transactionListenersAtEnd.getListeners();
        for (int i = 0; i < lastListeners.length; ++i) {
            final ITransactionListener l = (ITransactionListener) lastListeners[i];
            l.transactionEnded(transaction);
        }
    }

    protected void fireTransactionCancelled(Transaction transaction) {
        Object[] listeners = transactionListeners.getListeners();
        for (int i = 0; i < listeners.length; ++i) {
            final ITransactionListener l = (ITransactionListener) listeners[i];
            l.transactionCancelled(transaction);
        }
        Object[] lastListeners = transactionListenersAtEnd.getListeners();
        for (int i = 0; i < lastListeners.length; ++i) {
            final ITransactionListener l = (ITransactionListener) lastListeners[i];
            l.transactionCancelled(transaction);
        }
    }

    /**
     * Returns the plugin's redo action
     */
    public Action getRedoAction() {
        if (redoAction == null) {
            Image image = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_REDO);
            ImageDescriptor desc = ImageDescriptor.createFromImage(image);
            redoAction = new Action(ActionFactory.REDO.getId()) {
                public void run() {
                    revertLastTransaction(Transaction.REDO_TYPE);
                }
            };
            redoAction.setText("Redo");
            redoAction.setToolTipText("Redo the last modification");
            redoAction.setImageDescriptor(desc);
            redoAction.setEnabled(false);
            redoAction.setActionDefinitionId(ActionFactory.REDO.getCommandId());
        }
        return redoAction;
    }

    /**
     * Returns the plugin's undo action
     */
    public Action getUndoAction() {
        if (undoAction == null) {
            undoAction = new Action(ActionFactory.UNDO.getId()) {
                public void run() {
                    revertLastTransaction(Transaction.UNDO_TYPE);
                }
            };
            undoAction.setText("Undo");
            undoAction.setToolTipText("Undo the last modification");
            if (!CoreUtil.IsRunningHeadless) {
                Image image = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_UNDO);
                ImageDescriptor desc = ImageDescriptor.createFromImage(image);
                undoAction.setImageDescriptor(desc);
            }
            undoAction.setEnabled(false);
            undoAction.setActionDefinitionId(ActionFactory.UNDO.getCommandId());
        }
        return undoAction;
    }

    /**
     * Set the state of the undo/redo actions
     */
    public void setUndoRedoActionsState() {
        changeUndoActionState();
        changeRedoActionState();
    }

    /**
     * Set the state of the redo action
     * 
     * @return
     */
    private void changeRedoActionState() {
        // we call getRedoAction so that
        // the action is created if this
        // is the first time referencing it
        redoAction = getRedoAction();
        if (redoStack.size() != 0 && !redoAction.isEnabled()) {
            redoAction.setEnabled(true);
        } else if (redoStack.size() == 0 && redoAction.isEnabled()) {
            redoAction.setEnabled(false);
        }
    }

    /**
     * Set the state of the undo action
     * 
     * @return
     */
    private void changeUndoActionState() {
        // we call getUndoAction so that
        // the action is created if this
        // is the first time referencing it
        undoAction = getUndoAction();
        if (undoStack.size() != 0 && !undoAction.isEnabled()) {
            undoAction.setEnabled(true);
        } else if (undoStack.size() == 0 && undoAction.isEnabled()) {
            undoAction.setEnabled(false);
        }
    }

    /**
     * Listens for changes to the persistence file of the model for which this
     * manager is managing transactions. Such changes may be reason for this
     * manager to clear its undo and redo stacks.
     */
    private class ModelFileChangeListener implements IResourceChangeListener, IResourceDeltaVisitor {
        /*
         * (non-Javadoc)
         * 
         * @see
         * org.eclipse.core.resources.IResourceChangeListener#resourceChanged
         * (org.eclipse.core.resources.IResourceChangeEvent)
         * 
         * Get's the given event's delta visited by this listener.
         */
        public void resourceChanged(IResourceChangeEvent event) {
            // if the event has no delta, we aren't interested in it
            IResourceDelta delta = event.getDelta();
            if (delta == null)
                return;

            CorePlugin.logResourceActivity(delta);

            Job buildJob = Job.getJobManager().currentJob();
            if (buildJob != null && (buildJob.belongsTo(ResourcesPlugin.FAMILY_AUTO_BUILD)
                    || (buildJob.belongsTo(ResourcesPlugin.FAMILY_MANUAL_BUILD)
                            || buildJob.belongsTo(DelayedMarkerJob.FAMILY_DELAYED_MARKER_JOB)
                            || buildJob.belongsTo("System Data Type Upgrade") //$NON-NLS-1$
                            || buildJob.belongsTo(FAMILY_TRANSACTION)
                            || buildJob.belongsTo(ComponentTransactionListener.INTEGRITY_ISSUE_JOB_FAMILY)))) {
                return;
            }

            if (ignoreResourceChanges || ignoreResourceChangesMarker) {
                ignoreResourceChanges = false;
                return;
            }

            // get the event's delta visited by this manager
            try {
                delta.accept(this);
            } catch (CoreException e) {
                CorePlugin.logError("Could not respond to resource change event", e);
            }
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse
         * .core.resources.IResourceDelta)
         * 
         * Determines whether the given delta concerns the persistence file of
         * the model for which this manager is managing transactions. If that is
         * the case, this manager's undo and redo stacks are cleared.
         */
        public boolean visit(IResourceDelta delta) {
            // if the delta concerns the workspace or project root, return
            // that the delta's children should be visited
            IResource resource = delta.getResource();
            IPath path = resource.getFullPath();
            if (path.segmentCount() <= 1)
                return true;

            // if the delta concerns a folder in a project's root
            if (path.segmentCount() == 2) {
                // if the folder isn't the models folder
                if (!path.segment(1).equalsIgnoreCase(Ooaofooa.MODELS_DIRNAME)) {
                    // the delta is of no concern to us
                    return false;
                }

                // visit the children of the models folder
                return true;
            }

            if (!(resource instanceof IFile)) {
                // visit all folders
                return true;
            }

            // if the delta indicates that the model file for this manager's
            // model-root has changed, and it's not due to the model-root's
            // contents
            // being persisted

            IFile file = (IFile) resource;

            // we need to clear the stacks here
            if (Ooaofooa.MODELS_EXT.equalsIgnoreCase(file.getFileExtension())
                    && delta.getKind() == IResourceDelta.CHANGED && !isComponentOrChildrenPersisting()
                    && (delta.getKind() & IResourceDelta.CONTENT) == 0) {
                clearStacks();
                setUndoRedoActionsState();
            }
            return false;
        }
    }

    private boolean isComponentOrChildrenPersisting() {
        List<?> components = PersistenceManager.findAllComponents(Ooaofooa.getDefaultInstance(),
                SystemModel_c.class);
        for (Object component : components) {
            return isComponentOrChildrenPersisting((PersistableModelComponent) component);
        }
        return false;
    }

    private boolean isComponentOrChildrenPersisting(PersistableModelComponent component) {
        boolean persisting = component.isPersisting();
        if (!persisting) {
            for (Iterator<?> iterator = component.getChildren().iterator(); iterator.hasNext();) {
                PersistableModelComponent child = (PersistableModelComponent) iterator.next();
                if (isComponentOrChildrenPersisting(child)) {
                    return true;
                }
            }

        }
        return persisting;
    }

    public static void collectModelElementsNames(String elementType, String elementName) {
        if (!elementName.isEmpty() && !affectedModelElementsNames.contains(elementType + elementName))
            affectedModelElementsNames.add(elementType + elementName);
    }

    /**
     * Clears this manager's undo and redo stacks.
     */
    public void clearStacks() {
        undoStack.clear();
        redoStack.clear();
    }

    public static TransactionManager getSingleton() {
        if (singleton == null) {
            singleton = new TransactionManager();
            singleton.addTransactionListener(new ComponentTransactionListener());
            singleton.addTransactionListener(new PropertyViewListener());
        }
        return singleton;
    }

    public void setIgnoreResourceChange(boolean value) {
        ignoreResourceChanges = value;
    }

    public void processTransaction(Transaction transaction, boolean processNew) {
        ModelRoot[] modelRoots = transaction.getParticipatingModelRoots();
        for (int i = 0; i < modelRoots.length; i++) {

            IModelDelta[] deltas = transaction.getDeltas(modelRoots[i]);
            // process each delta set
            processDeltas(processNew, deltas, modelRoots[i]);
        }

    }

    public static void processDeltas(boolean processNew, IModelDelta[] deltas, ModelRoot modelRoot) {
        List<Object> processedProxies = new ArrayList<Object>();
        for (int j = 0; j < deltas.length; j++) {
            // skip any setRepresents deltas
            if (!(modelRoot instanceof Ooaofooa) && deltas[j] instanceof AttributeChangeModelDelta) {
                AttributeChangeModelDelta acmd = (AttributeChangeModelDelta) deltas[j];
                if (acmd.getAttributeName().equals("Represents")) { //$NON-NLS-1$
                    continue;
                }
            }
            ModelElement modelElement = deltas[j].getModelElement();
            NonRootModelElement nrme = (NonRootModelElement) modelElement;
            if (nrme == null) {
                CorePlugin.logError("Found null instance in model delta.", null);
                continue;
            }

            // process any deletions contained in the
            // delta set
            if (deltas[j].getKind() == Modeleventnotification_c.DELTA_DELETE) {
                if (nrme.delete()) {
                    Ooaofooa.getDefaultInstance().fireModelElementDeleted(
                            new BaseModelDelta(Modeleventnotification_c.DELTA_DELETE, nrme));
                }

                // remove the element from the current selection,
                // as would be done if the deletion was done by a
                // delete-action
                Selection.getInstance().removeFromSelection(nrme);
            }

            // process attribute changes
            if (deltas[j] instanceof AttributeChangeModelDelta) {
                AttributeChangeModelDelta delta = (AttributeChangeModelDelta) deltas[j];
                String setMethod = "set" + delta.getAttributeName(); // $NON-NLS-1$
                Method method = null;
                try {
                    Object newValue = delta.getNewValue();
                    Class<?> attributeClass = newValue != null
                            ? Transaction.getPrimitiveClass(delta.getNewValue().getClass())
                            : new Object().getClass();
                    method = nrme.getClass().getMethod(setMethod, new Class[] { attributeClass });
                    Object[] arg = { delta.getNewValue() };
                    method.invoke(modelElement, arg);
                } catch (Exception e) {
                    CorePlugin.logError("Unable to find or invoke set method.", e);
                }
            }

            // process relationship changes
            if (deltas[j] instanceof RelationshipChangeModelDelta) {
                RelationshipChangeModelDelta delta = (RelationshipChangeModelDelta) deltas[j];
                NonRootModelElement dest = (NonRootModelElement) delta.getDestinationModelElement();

                String relateDirection = "To"; // $NON-NLS-1$
                String relationship = "relate"; // $NON-NLS-1$

                if (delta.getKind() == Modeleventnotification_c.DELTA_ELEMENT_UNRELATED) {
                    relateDirection = "From"; // $NON-NLS-1$
                    relationship = "unrelate"; // $NON-NLS-1$
                }

                // build the method name from the delta information
                String relateMethod = relationship + "AcrossR" // $NON-NLS-1$
                        + delta.getRelationName() + relateDirection + delta.getRelationDirectionPhrase();

                // find the method of that name and invoke it
                try {
                    Class<?> destClass = nrme.getClass();
                    if (dest != null) {
                        destClass = dest.getClass();
                    } else {
                        Method[] methods = nrme.getClass().getMethods();
                        for (Method method : methods) {
                            if (method.getName().equals(relateMethod)) {
                                destClass = method.getParameterTypes()[0];
                                break;
                            }
                        }
                    }
                    Method method = nrme.getClass().getMethod(relateMethod, new Class[] { destClass });
                    Object[] arg = new Object[1];
                    arg[0] = dest;
                    method.invoke(nrme, arg);
                } catch (Exception e) {
                    CorePlugin.logError("Unable to find or invoke relate method.", e);
                }
            }

            if (processNew) {
                // process creations
                if (deltas[j].getKind() == Modeleventnotification_c.DELTA_NEW) {
                    // merges can produce multiple copies of proxies, only create
                    // one new delta
                    if (nrme.isProxy()) {
                        if (processedProxies.contains(nrme)) {
                            continue;
                        } else {
                            processedProxies.add(nrme);
                        }
                    }
                    // skip any new deltas that still have a compare root, the compare
                    // may have required temporary proxy elements that were treated
                    // as news but exist in the target already
                    if (nrme.getModelRoot().isCompareRoot()) {
                        continue;
                    }
                    InstanceList instanceList = nrme.getModelRoot().getInstanceList(nrme.getClass());
                    synchronized (instanceList) {
                        instanceList.add(nrme);
                    }
                    nrme.addInstanceToMap(nrme.getInstanceKey());
                    if (nrme instanceof ActiveObject_c)
                        Activepoller_c.register((ActiveObject_c) nrme);
                    if (nrme.getModelRoot() instanceof Ooaofooa) {
                        Ooaofooa.getDefaultInstance().fireModelElementCreated(
                                new BaseModelDelta(Modeleventnotification_c.DELTA_NEW, nrme));
                    } else {
                        ModelRoot graphicsRoot = (ModelRoot) OoaofgraphicsUtil.getGraphicsRoot(
                                Ooaofooa.DEFAULT_WORKING_MODELSPACE, OoaofgraphicsUtil.getGraphicsClass());
                        graphicsRoot.fireModelElementCreated(
                                new BaseModelDelta(Modeleventnotification_c.DELTA_NEW, nrme));
                    }
                }
            }
        }

    }

    public void endCollectingFor(Transaction transaction) {
        transaction.deltaCollector.endCollecting();
        activeTransaction = null;
    }

}