Ptidej-v5.2-master/POM Tests/data/ArgoUML_0.28/src/org/argouml/ui/targetmanager/TargetManager.java
// $Id: TargetManager.java 16651 2009-01-18 19:54:02Z tfmorris $
// Copyright (c) 2002-2009 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies.  This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason.  IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package org.argouml.ui.targetmanager;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import javax.management.ListenerNotFoundException;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import javax.swing.event.EventListenerList;

import org.apache.log4j.Logger;
import org.argouml.kernel.Project;
import org.argouml.kernel.ProjectManager;
import org.argouml.model.Model;
import org.tigris.gef.base.Diagram;
import org.tigris.gef.presentation.Fig;

/**
 * The manager of the target of ArgoUML.
 * The target of ArgoUML is the element currently selected by the user.
 * This can either be an instance of a meta-class (an
 * Interface or a Class for example) but it can also be a diagram
 * or anything that is shown
 * on a diagram.<p>
 *
 * There can be multiple targets in case
 * someone selected multiple items in the explorer or on the diagram.
 * This can be done by shift-clicking or Ctrl-clicking items,
 * or by drawing a box on the diagram around the items to select.<p>
 *
 * In case multiple targets are selected, the target manager will add each
 * target to the beginning of the list of targets. This way,
 * the first item of the list is the last selected item.
 * Most functions in ArgoUML work on all selected items.
 * However, a few (intentionally) only work on one target,
 * such as the properties panels.
 * These functions have 2 ways of retrieving the target they should work on:
 * <ul>
 * <li>1. Use the functions that return one target only,
 * such as getTarget(), getModelTarget(), getFigTarget().
 * <li>2. Use the first item in the list returned by
 * getTargets(), getModelTargets(). </ul><p>
 *
 * Remark: There is currently no function getFigs(),
 * returning a list of selected figs.
 * But you can obtain such a list from GEF. <p>
 *
 * The purpose of the targetmanager is to have a central spot where we
 * manage the list of current targets.<p>
 *
 * Via an event mechanism this manager makes sure that all objects interested
 * in knowing wether the selection changed are acknowledged. <p>
 *
 * Note in particular that null is an invalid target.<p>
 *
 * Thanks to the architecture of ArgoUML of Modelelements and Figs,
 * one rule has been decided upon (by mvw@tigris.org):<ul>
 * <li>The list of targets shall not contain any Fig that has an owner.
 * Instead, the owner is enlisted.
 * </ul>
 *
 * @author jaap.branderhorst@xs4all.nl
 */
public final class TargetManager {

    /**
     * The manager of the history of targets. Every time the user (or
     * the program) selects a new target, this is recorded in the
     * history. Via navigateBack and navigateForward, the user can
     * browse through the history just like in an ordinary internet
     * browser.
     * @author jaap.branderhorst@xs4all.nl
     */
    private final class HistoryManager implements TargetListener {

        private static final int MAX_SIZE = 100;

        /**
         * The history with targets.
         */
        private List history = new ArrayList();

        /**
         * Flag to indicate if the current settarget was instantiated by a
         * navigateBack action.
         */
        private boolean navigateBackward;

        /**
         * The pointer to the current target in the history.
         */
        private int currentTarget = -1;

        /**
         * The listener to UML model changes. 
         * Deleted model elements are removed 
         * from the history list. 
         */
        private Remover umlListener = new HistoryRemover(); 

        /**
         * Default constructor that registrates the history manager as target
         * listener with the target manager.
         *
         */
        private HistoryManager() {
            addTargetListener(this);
        }

        /**
         * Puts some target into the history (if needed). Updates both
         * the history as the pointer to indicate the target.
         * @param target The target to put into the history
         */
        private void putInHistory(Object target) {
            if (currentTarget > -1) {
                // only targets we didn't have allready count
                Object theModelTarget =
                    target instanceof Fig ? ((Fig) target).getOwner() : target;
                Object oldTarget =
                    ((WeakReference) history.get(currentTarget)).get();
                oldTarget =
                    oldTarget instanceof Fig
		    ? ((Fig) oldTarget).getOwner()
		    : oldTarget;
                if (oldTarget == theModelTarget) {
                    return;
                }
            }
            if (target != null && !navigateBackward) {
                if (currentTarget + 1 == history.size()) {
                    umlListener.addListener(target);
                    history.add(new WeakReference(target));
                    currentTarget++;
                    resize();
                } else {
                    WeakReference ref =
                        currentTarget > -1
			? (WeakReference) history.get(currentTarget)
			: null;
                    if (currentTarget == -1 || !ref.get().equals(target)) {
                        int size = history.size();
                        for (int i = currentTarget + 1; i < size; i++) {
                            umlListener.removeListener(
                                    history.remove(currentTarget + 1));
                        }
                        history.add(new WeakReference(target));
                        umlListener.addListener(target);
                        currentTarget++;
                    }
                }

            }
        }

        /**
         * Resizes the history if it's grown too big.
         *
         */
        private void resize() {
            int size = history.size();
            if (size > MAX_SIZE) {
                int oversize = size - MAX_SIZE;
                int halfsize = size / 2;
                if (currentTarget > halfsize && oversize < halfsize) {
                    for (int i = 0; i < oversize; i++) {
                        umlListener.removeListener(
                                history.remove(0));
                    }
                    currentTarget -= oversize;
                }
            }
        }

        /**
         * Navigate one target forward in history. Throws an
         * illegalstateException if not possible.
         *
         */
        private void navigateForward() {
            if (currentTarget >= history.size() - 1) {
                throw new IllegalStateException(
			"NavigateForward is not allowed "
			+ "since the targetpointer is pointing at "
			+ "the upper boundary "
			+ "of the history");
            }
            setTarget(((WeakReference) history.get(++currentTarget)).get());
        }

        /**
         * Navigate one step back in history. Throws an illegalstateexception if
         * not possible.
         *
         */
        private void navigateBackward() {
            if (currentTarget == 0) {
                throw new IllegalStateException(
		        "NavigateBackward is not allowed "
			+ "since the targetpointer is pointing at "
			+ "the lower boundary "
			+ "of the history");
            }
            navigateBackward = true;
            // If nothing selected, go to last selected target
            if (targets.size() == 0) {
                setTarget(((WeakReference) history.get(currentTarget)).get());
            } else {
                setTarget(((WeakReference) history.get(--currentTarget)).get());
            }
            navigateBackward = false;
        }

        /**
         * Checks if it's possible to navigate back.
         * 
         * @return true if it's possible to navigate back.
         */
        private boolean navigateBackPossible() {
            return currentTarget > 0;
        }

        /**
         * Checks if it's possible to navigate forward.
         *
         * @return true if it's possible to navigate forward
         */
        private boolean navigateForwardPossible() {
            return currentTarget < history.size() - 1;
        }

        /**
         * Listener for additions of targets to the selected targets.
         * On addition of targets we put them in the history.
         * @see org.argouml.ui.targetmanager.TargetListener#targetAdded(
         * org.argouml.ui.targetmanager.TargetEvent)
         */
        public void targetAdded(TargetEvent e) {
            Object[] addedTargets = e.getAddedTargets();
            // we put the targets 'backwards' in the history
            // since the first target in the addedTargets array is
            // the first one selected.
            for (int i = addedTargets.length - 1; i >= 0; i--) {
                putInHistory(addedTargets[i]);
            }
        }

        /**
         * Listener for the removal of targets from the selection.
         * On removal of a target from the selection we do nothing
         * with respect to the history of targets.
         * @see org.argouml.ui.targetmanager.TargetListener#targetRemoved(org.argouml.ui.targetmanager.TargetEvent)
         */
        public void targetRemoved(TargetEvent e) {
        }

        /**
         * Listener for the selection of a whole bunch of targets
         * in one go (or just one). Puts all the new
         * targets in the history starting with the 'newest' target.
         * @see org.argouml.ui.targetmanager.TargetListener#targetSet(org.argouml.ui.targetmanager.TargetEvent)
         */
        public void targetSet(TargetEvent e) {
            Object[] newTargets = e.getNewTargets();
            for (int i = newTargets.length - 1; i >= 0; i--) {
                putInHistory(newTargets[i]);
            }
        }

        /**
         * Cleans the history in total.
         *
         */
        private void clean() {
            umlListener.removeAllListeners(history);
            history = new ArrayList();
            currentTarget = -1;
        }

        private void removeHistoryTarget(Object o) {
            if (o instanceof Diagram) {
                Iterator it = ((Diagram) o).getEdges().iterator();
                while (it.hasNext()) {
                    removeHistoryTarget(it.next());
                }
                it = ((Diagram) o).getNodes().iterator();
                while (it.hasNext()) {
                    removeHistoryTarget(it.next());
                }
            }
            ListIterator it = history.listIterator();
            while (it.hasNext()) {
                WeakReference ref = (WeakReference) it.next();
                Object historyObject = ref.get();
                if (Model.getFacade().isAModelElement(o)) {
                    historyObject =
                        historyObject instanceof Fig
			? ((Fig) historyObject).getOwner()
			: historyObject;

                }
                if (o == historyObject) {
                    if (history.indexOf(ref) <= currentTarget) {
                        currentTarget--;
                    }
                    it.remove();
                }

                // cannot break here since an object can be multiple
                // times in history
            }
        }

    }
    /**
     * The log4j logger to log messages to.
     */
    private static final Logger LOG = Logger.getLogger(TargetManager.class);

    /**
     * The singleton instance.
     */
    private static TargetManager instance = new TargetManager();

    /**
     * The targets.
     */
    private List targets = new ArrayList();

    /**
     * Cache for the modeltarget. See getModelTarget.
     */
    private Object modelTarget;

    /**
     * Cache for the figTarget. See getFigTarget.
     */
    private Fig figTarget;

    /**
     * The list with targetlisteners.
     */
    private EventListenerList listenerList = new EventListenerList();

    /**
     * The history manager of argouml. Via the historymanager browser behaviour
     * is emulated.
     */
    private HistoryManager historyManager = new HistoryManager();
    
    /**
     * The listener to UML model changes. 
     * Deleted model elements are removed 
     * from the target list. 
     */
    private Remover umlListener = new TargetRemover(); 

    /**
     * Flag to indicate that there is a setTarget method running.
     */
    private boolean inTransaction = false;

    /**
     * Singleton retrieval method.
     * @return the targetmanager
     */
    public static TargetManager getInstance() {
        return instance;
    }

    /**
     * Singleton constructor should remain private.
     */
    private TargetManager() {
    }

    /**
     * Sets the targets to the single given object. If there are targets at the
     * moment of calling this method, these will be removed as targets. To
     * all interested targetlisteners, a TargetEvent will be fired. If the
     * new target o equals the current target, no events will be fired, nor will
     * the target be (re)set.
     * @param o The new target, null clears all targets.
     */
    public synchronized void setTarget(Object o) {
	if (isInTargetTransaction()) {
            return;
        }

	if ((targets.size() == 0 && o == null)
	        || (targets.size() == 1 && targets.get(0).equals(o))) {
            return;
        }

	startTargetTransaction();

	Object[] oldTargets = targets.toArray();
        umlListener.removeAllListeners(targets);
	targets.clear();

	if (o != null) {
            Object newTarget;
            if (o instanceof Diagram) { // Needed for Argo startup :-(
                newTarget = o;
            } else {
	        newTarget = getOwner(o);
            }
            targets.add(newTarget);
            umlListener.addListener(newTarget);
        }
	internalOnSetTarget(TargetEvent.TARGET_SET, oldTargets);

        endTargetTransaction();
    }

    private void internalOnSetTarget(String eventName, Object[] oldTargets) {
	TargetEvent event =
	    new TargetEvent(this, eventName, oldTargets, targets.toArray());

	if (targets.size() > 0) {
	    figTarget = determineFigTarget(targets.get(0));
	    modelTarget = determineModelTarget(targets.get(0));
	} else {
	    figTarget = null;
	    modelTarget = null;
	}

	if (TargetEvent.TARGET_SET.equals(eventName)) {
	    fireTargetSet(event);
	    return;
	} else if (TargetEvent.TARGET_ADDED.equals(eventName)) {
	    fireTargetAdded(event);
	    return;
	} else if (TargetEvent.TARGET_REMOVED.equals(eventName)) {
	    fireTargetRemoved(event);
	    return;
	}

	LOG.error("Unknown eventName: " + eventName);
    }

    /**
     * Returns the current primary target, the first selected object.
     *
     * The value will be that of the new primary target during a targetSet/
     * targetAdded/targetRemoved notification, since they are just that,
     * notifications that the target(s) has just changed.
     *
     * @return The current target, or null if no target is selected
     */
    public synchronized Object getTarget() {
	return targets.size() > 0 ? targets.get(0) : null;
    }

    /**
     * Sets the given collection to the current targets. If the collection
     * equals the current targets, then does nothing. When setting
     * the targets, a TargetEvent will be fired to each interested listener.
     * Note that the first element returned by an Iterator on targetList
     * will be taken to be the primary target (see getTarget()), and that
     * an event will be fired also in case that that element would not equal
     * the element returned by getTarget().
     * Note also that any nulls within the Collection will be ignored.
     *
     * @param targetsCollection The new targets list.
     */
    public synchronized void setTargets(Collection targetsCollection) {
	Iterator ntarg;

	if (isInTargetTransaction()) {
            return;
        }

        Collection targetsList = new ArrayList();
        if (targetsCollection != null) {
            targetsList.addAll(targetsCollection);
        }

        /* Remove duplicates and take care of getOwner()
         * and remove nulls: */
        List modifiedList = new ArrayList();
        Iterator it = targetsList.iterator();
        while (it.hasNext()) {
            Object o = it.next();
            o = getOwner(o);
            if ((o != null) && !modifiedList.contains(o)) {
                modifiedList.add(o);
            }
        }
        targetsList = modifiedList;

	Object[] oldTargets = null;

	// check if there are new elements in the list
	// if the old and new list are of the same size
	// set the oldTargets to the correct selection
	if (targetsList.size() == targets.size()) {
	    boolean first = true;
	    ntarg = targetsList.iterator();

	    while (ntarg.hasNext()) {
		Object targ = ntarg.next();
		if (targ == null) {
                    continue;
                }
		if (!targets.contains(targ)
		    || (first && targ != getTarget())) {
		    oldTargets = targets.toArray();
		    break;
		}
                first = false;
	    }
	} else {
            oldTargets = targets.toArray();
        }

	if (oldTargets == null) {
            return;
        }

	startTargetTransaction();

        umlListener.removeAllListeners(targets);
	targets.clear();

	// implement set-like behaviour. The same element
	// may not be added more then once.
	ntarg = targetsList.iterator();
	while (ntarg.hasNext()) {
	    Object targ = ntarg.next();
	    if (targets.contains(targ)) {
                continue;
            }
	    targets.add(targ);
            umlListener.addListener(targ);
	}

	internalOnSetTarget(TargetEvent.TARGET_SET, oldTargets);

	endTargetTransaction();
    }

    /**
     * Adds a target to the targets list. If the target is already in
     * the targets list then does nothing. Otherwise the
     * target will be added and an appropriate TargetEvent will be
     * fired to all interested listeners. Since null can never be a target,
     * adding null will never do anything.
     * @param target the target to be added.
     */
    public synchronized void addTarget(Object target) {
        if (target instanceof TargetListener) {
            LOG.warn("addTarget method received a TargetListener, "
                    + "perhaps addTargetListener was intended! - " + target);
        }
	if (isInTargetTransaction()) {
            return;
        }
        Object newTarget = getOwner(target);

        if (target == null
                || targets.contains(target)
                || targets.contains(newTarget)) {
            return;
        }

	startTargetTransaction();

	Object[] oldTargets = targets.toArray();
	targets.add(0, newTarget);
        umlListener.addListener(newTarget);

	internalOnSetTarget(TargetEvent.TARGET_ADDED, oldTargets);

	endTargetTransaction();
    }

    /**
     * Removes the target from the targets list. Does nothing if the target
     * does not exist in the targets list. Fires an appropriate TargetEvent to
     * all interested listeners. Since null can never be a target, removing
     * null will never do anything.
     * @param target The target to remove.
     */
    public synchronized void removeTarget(Object target) {
        if (isInTargetTransaction()) {
            return;
        }

	if (target == null /*|| !targets.contains(target)*/) {
            return;
        }

	startTargetTransaction();

	Object[] oldTargets = targets.toArray();
        Collection c = getOwnerAndAllFigs(target);
//	targets.remove(target);
        targets.removeAll(c);
        umlListener.removeAllListeners(c);

        if (targets.size() != oldTargets.length) {
            internalOnSetTarget(TargetEvent.TARGET_REMOVED, oldTargets);
        }

	endTargetTransaction();
    }

    private Collection getOwnerAndAllFigs(Object o) {
        Collection c = new ArrayList();
        c.add(o);
        if (o instanceof Fig) {
            if (((Fig) o).getOwner() != null) {
                o = ((Fig) o).getOwner();
                c.add(o);
            }
        }
        if (!(o instanceof Fig)) {
            Project p = ProjectManager.getManager().getCurrentProject();
            Collection col = p.findAllPresentationsFor(o);
            if (col != null && !col.isEmpty()) {
                c.addAll(col);
            }
        }
        return c;
    }

    /**
     * @param o the object
     * @return the owner of the fig, or if it didn't exist, the object itself
     */
    public Object getOwner(Object o) {
        if (o instanceof Fig) {
            if (((Fig) o).getOwner() != null) {
                o = ((Fig) o).getOwner();
            }
        }
        return o;
    }

    /**
     * Returns a list of all targets. Returns an empty list
     * if there are no targets. If there are several targets then the first
     * Object by an Iterator on the returned List or the zero'th Object
     * in an array on this List is guaranteed to be the object returned
     * by {@link #getSingleTarget()}.
     *
     * The value will be that of the new target(s) during a targetSet/
     * targetAdded/targetRemoved notification, since they are just that,
     * notifications that the target(s) has just changed.
     *
     * @return A list with all targets.
     */
    public synchronized List getTargets() {
        return Collections.unmodifiableList(targets);
    }

    /**
     * If there is only one target, then it is returned.
     * Otherwise null.
     * 
     * @return the one and only target
     */
    public synchronized Object getSingleTarget() {
        return targets.size() == 1 ? targets.get(0) : null;
    }
    
    /**
     * @return the target from the model
     */
    public synchronized Collection getModelTargets() {
        ArrayList t = new ArrayList();
        Iterator iter = getTargets().iterator();
        while (iter.hasNext()) {
            t.add(determineModelTarget(iter.next()));
        }
        return t;
    }

    /**
     * If there is only one model target, then it is returned.
     * Otherwise null.
     *
     * @return the single model target
     */
    public synchronized Object getSingleModelTarget() {
        int i = 0;
        Iterator iter = getTargets().iterator();
        while (iter.hasNext()) {
            if (determineModelTarget(iter.next()) != null) {
                i++;
            }
            if (i > 1) {
                break;
            }
        }
        if (i == 1) {
            return modelTarget;
        }
        return null;
    }

    /**
     * Adds a listener.
     * @param listener the listener to add
     */
    public void addTargetListener(TargetListener listener) {
        listenerList.add(TargetListener.class, listener);
    }

    /**
     * Removes a listener.
     * @param listener the listener to remove
     */
    public void removeTargetListener(TargetListener listener) {
        listenerList.remove(TargetListener.class, listener);
    }

    private void fireTargetSet(TargetEvent targetEvent) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
	    try {
	        if (listeners[i] == TargetListener.class) {
	            // Lazily create the event:
	            ((TargetListener) listeners[i + 1]).targetSet(targetEvent);
	        }
	    } catch (RuntimeException e) {
	        LOG.error("While calling targetSet for "
	                + targetEvent
	                + " in "
	                + listeners[i + 1]
	                            + " an error is thrown.",
	                            e);
                e.printStackTrace();
            }
        }
    }

    private void fireTargetAdded(TargetEvent targetEvent) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
	    try {
		if (listeners[i] == TargetListener.class) {
		    // Lazily create the event:
		    ((TargetListener) listeners[i + 1])
		        .targetAdded(targetEvent);
		}
	    } catch (RuntimeException e) {
		LOG.error("While calling targetAdded for "
			  + targetEvent
			  + " in "
			  + listeners[i + 1]
			  + " an error is thrown.",
			  e);
                e.printStackTrace();
	    }
        }
    }

    private void fireTargetRemoved(TargetEvent targetEvent) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
	    try {
		if (listeners[i] == TargetListener.class) {
		    // Lazily create the event:
		    ((TargetListener) listeners[i + 1])
		        .targetRemoved(targetEvent);
		}
	    } catch (RuntimeException e) {
		LOG.warn("While calling targetRemoved for "
			  + targetEvent
			  + " in "
			  + listeners[i + 1]
			  + " an error is thrown.",
			  e);
	    }
        }
    }

    private void startTargetTransaction() {
        inTransaction = true;
    }

    private boolean isInTargetTransaction() {
        return inTransaction;
    }

    private void endTargetTransaction() {
        inTransaction = false;
    }

    /**
     * Convenience method to return the target as fig. If the current
     * target (retrieved by getTarget) is either a fig itself or the
     * owner of a fig this fig will be returned. Otherwise null will
     * be returned.
     * @return the target in it's 'fig-form'
     */
    public Fig getFigTarget() {
        return figTarget;
    }

    /**
     * Calculates the most probable 'fig-form' of some target. Beware:
     * The result does NOT depend on the current diagram!
     *
     * @param target the target to calculate the 'fig-form' for.
     * @return The fig-form.
     */
    private Fig determineFigTarget(Object target) {
        if (!(target instanceof Fig)) {

            Project p = ProjectManager.getManager().getCurrentProject();
            Collection col = p.findFigsForMember(target);
            if (col == null || col.isEmpty()) {
                target = null;
            } else {
                target = col.iterator().next();
            }
        }

        return target instanceof Fig ? (Fig) target : null;
    }

    /**
     * Returns the target in it's 'modelform'. If the target retrieved
     * by getTarget is an UMLDiagram or a UML element the target will
     * be returned. If the target is a fig but owned by a modelelement
     * that modelelement will be returned.  Otherwise null will be
     * returned.
     * 
     * @return the target in it's 'modelform'.
     */
    public Object getModelTarget() {
        return modelTarget;

    }

    /**
     * Calculates the modeltarget.
     * @param target The target to calculate the modeltarget for
     * @return The modeltarget
     */
    private Object determineModelTarget(Object target) {
        if (target instanceof Fig) {
            Object owner = ((Fig) target).getOwner();
            if (Model.getFacade().isAUMLElement(owner)) {
                target = owner;
            }
        }
        return target instanceof Diagram
            || Model.getFacade().isAUMLElement(target) ? target : null;

    }

    /**
     * Navigates the target pointer one target forward. This implements together
     * with navigateBackward browser like functionality.
     * @throws IllegalStateException If the target pointer is at the end of the
     * history.
     */
    public void navigateForward() throws IllegalStateException {
        historyManager.navigateForward();
        LOG.debug("Navigate forward");
    }

    /**
     * Navigates the target pointer one target backward. This
     * implements together with navigateForward browser like
     * functionality
     * @throws IllegalStateException If the target pointer is at the
     * beginning of the history.
     */
    public void navigateBackward() throws IllegalStateException {
        historyManager.navigateBackward();
        LOG.debug("Navigate backward");

    }

    /**
     * Checks if it's possible to navigate forward.
     * @return true if it is possible to navigate forward.
     */
    public boolean navigateForwardPossible() {
        return historyManager.navigateForwardPossible();
    }

    /**
     * Checks if it's possible to navigate backward.
     *
     * @return true if it's possible to navigate backward
     */
    public boolean navigateBackPossible() {
        return historyManager.navigateBackPossible();
    }

    /**
     * Cleans the history. Needed for the JUnit tests and when instantiating a
     * new project.
     */
    public void cleanHistory() {
        historyManager.clean();
    }

    /**
     * @param o the object to be removed
     */
    public void removeHistoryElement(Object o) {
        historyManager.removeHistoryTarget(o);
    }

    /**
     * The listener to removals of UML model elements, 
     * diagrams and CommentEdges. 
     * Deleted elements are removed 
     * from the target list and/or from the history. 
     * 
     * @author michiel
     */
    private abstract class Remover implements PropertyChangeListener, 
        NotificationListener 
    {
        
        protected Remover() {
            // Listen for the removal of diagrams from project
            ProjectManager.getManager().addPropertyChangeListener(this);
        }

        private void addListener(Object o) {
            if (Model.getFacade().isAModelElement(o)) {
                Model.getPump().addModelEventListener(this, o, "remove");
            } else if (o instanceof Diagram) {
                // Figs on a diagram without an owning model element 
                ((Diagram) o).addPropertyChangeListener(this);
            } else if (o instanceof NotificationEmitter) {
                // CommentEdge - the owner of a FigEdgeNote
                ((NotificationEmitter) o).addNotificationListener(
                        this, null, o);
            }
        }

        private void removeListener(Object o) {
            if (Model.getFacade().isAModelElement(o)) {
                Model.getPump().removeModelEventListener(this, o, "remove");
            } else if (o instanceof Diagram) {
                ((Diagram) o).removePropertyChangeListener(this);
            } else if (o instanceof NotificationEmitter) {
                try {
                    ((NotificationEmitter) o).removeNotificationListener(this);
                } catch (ListenerNotFoundException e) {
                    LOG.error("Notification Listener for "
                                + "CommentEdge not found", e);
                }
            }
        }

        private void removeAllListeners(Collection c) {
            Iterator i = c.iterator();
            while (i.hasNext()) {
                removeListener(i.next());
            }
        }

        /*
         * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
         */
        public void propertyChange(PropertyChangeEvent evt) {
            if ("remove".equals(evt.getPropertyName())) {
                remove(evt.getSource());
            }
        }

        /*
         * @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
         */
        public void handleNotification(Notification notification, 
                Object handback) {
            if ("remove".equals(notification.getType())) {
                remove(notification.getSource());
            }
            
        }
        
        protected abstract void remove(Object obj);
    }

    private class TargetRemover extends Remover {
        protected void remove(Object obj) {
            removeTarget(obj);
        }
    }

    private class HistoryRemover extends Remover {
        protected void remove(Object obj) {
            historyManager.removeHistoryTarget(obj);
        }
    }
}

