/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.core.commands.common.EventManager;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;
import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IElementFactory;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetElementAdapter;
import org.eclipse.ui.IWorkingSetManager;
import org.eclipse.ui.IWorkingSetUpdater;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.dialogs.IWorkingSetEditWizard;
import org.eclipse.ui.dialogs.IWorkingSetNewWizard;
import org.eclipse.ui.dialogs.IWorkingSetPage;
import org.eclipse.ui.dialogs.IWorkingSetSelectionDialog;
import org.eclipse.ui.internal.dialogs.WorkingSetEditWizard;
import org.eclipse.ui.internal.dialogs.WorkingSetNewWizard;
import org.eclipse.ui.internal.dialogs.WorkingSetSelectionDialog;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
import org.eclipse.ui.internal.registry.WorkingSetDescriptor;
import org.eclipse.ui.internal.registry.WorkingSetRegistry;
import org.eclipse.ui.statushandlers.StatusManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;


/**
 * Abstract implementation of <code>IWorkingSetManager</code>.
 */
public abstract class AbstractWorkingSetManager extends EventManager implements
		IWorkingSetManager, BundleListener, IExtensionChangeHandler {
	
    private SortedSet workingSets = new TreeSet(WorkingSetComparator.INSTANCE);
    
    /**
     * Size of the list of most recently used working sets.
     */
    private static final int MRU_SIZE = 5;
    private List recentWorkingSets = new ArrayList();

    private BundleContext bundleContext;
    private Map/*<String, IWorkingSetUpdater>*/ updaters= new HashMap();

	private Map/*<String, IWorkingSetElementAdapter>*/ elementAdapters = new HashMap();
    
    private static final IWorkingSetUpdater NULL_UPDATER= new IWorkingSetUpdater() {
		public void add(IWorkingSet workingSet) {
		}
		public boolean remove(IWorkingSet workingSet) {
			return true;
		}
		public boolean contains(IWorkingSet workingSet) {
			return true;
		}
		public void dispose() {
		}
	};
	
	private static final IWorkingSetElementAdapter IDENTITY_ADAPTER = new IWorkingSetElementAdapter() {

		public IAdaptable[] adaptElements(IWorkingSet ws, IAdaptable[] elements) {
			return elements;
		}

		public void dispose() {
		}
	};
		
    /**
     * Returns the descriptors for the given editable working set ids. If an id
     * refers to a missing descriptor, or one that is non-editable, it is
     * skipped. If <code>null</code> is passed, all editable descriptors are
     * returned.
     * 
     * @param supportedWorkingSetIds
     *            the ids for the working set descriptors, or <code>null</code>
     *            for all editable descriptors
     * @return the descriptors corresponding to the given editable working set
     *         ids
     */
    private static WorkingSetDescriptor[] getSupportedEditableDescriptors(
            String[] supportedWorkingSetIds) {
        WorkingSetRegistry registry = WorkbenchPlugin.getDefault()
                .getWorkingSetRegistry();
        if (supportedWorkingSetIds == null) {
            return registry.getNewPageWorkingSetDescriptors();
        }
        List result = new ArrayList(supportedWorkingSetIds.length);
        for (int i = 0; i < supportedWorkingSetIds.length; i++) {
            WorkingSetDescriptor desc = registry
                    .getWorkingSetDescriptor(supportedWorkingSetIds[i]);
            if (desc != null && desc.isEditable()) {
                result.add(desc);
            }
        }
        return (WorkingSetDescriptor[]) result
                .toArray(new WorkingSetDescriptor[result.size()]);
    }
    
    protected AbstractWorkingSetManager(BundleContext context) {
    	bundleContext= context;
    	bundleContext.addBundleListener(this);
    	PlatformUI.getWorkbench().getExtensionTracker().registerHandler(this, ExtensionTracker
				.createExtensionPointFilter(getExtensionPointFilter()));
	}
    
    /**
	 * 
	 * @return
	 * @since 3.3
	 */
	private IExtensionPoint getExtensionPointFilter() {
		return Platform.getExtensionRegistry().getExtensionPoint(
				PlatformUI.PLUGIN_ID,
				IWorkbenchRegistryConstants.PL_WORKINGSETS);
	}
    
    public void dispose() {
    	bundleContext.removeBundleListener(this);
    	for (Iterator iter= updaters.values().iterator(); iter.hasNext();) {
			((IWorkingSetUpdater)iter.next()).dispose();
		}
    	
    	for (Iterator iter= elementAdapters.values().iterator(); iter.hasNext();) {
			((IWorkingSetElementAdapter)iter.next()).dispose();
		}
    }
    
    //---- working set creation -----------------------------------------------------
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public IWorkingSet createWorkingSet(String name, IAdaptable[] elements) {
        return new WorkingSet(name, name, elements);
    }
    
    public IWorkingSet createAggregateWorkingSet(String name, String label,
			IWorkingSet[] components) {
		return new AggregateWorkingSet(name, label, components);
	}

    /*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IWorkingSetManager
	 */
    public IWorkingSet createWorkingSet(IMemento memento) {
        return restoreWorkingSet(memento);
    }

    //---- working set management ---------------------------------------------------
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public void addWorkingSet(IWorkingSet workingSet) {
        Assert.isTrue(!workingSets.contains(workingSet),
                "working set already registered"); //$NON-NLS-1$
        internalAddWorkingSet(workingSet);
    }

    private void internalAddWorkingSet(IWorkingSet workingSet) {
		workingSets.add(workingSet);
        ((AbstractWorkingSet)workingSet).connect(this);
        addToUpdater(workingSet);
        firePropertyChange(CHANGE_WORKING_SET_ADD, null, workingSet);
	}

	/* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    protected boolean internalRemoveWorkingSet(IWorkingSet workingSet) {
        boolean workingSetRemoved = workingSets.remove(workingSet);
        boolean recentWorkingSetRemoved = recentWorkingSets.remove(workingSet);
        
        if (workingSetRemoved) {
        	((AbstractWorkingSet)workingSet).disconnect();
        	removeFromUpdater(workingSet);
            firePropertyChange(CHANGE_WORKING_SET_REMOVE, workingSet, null);
        }
        return workingSetRemoved || recentWorkingSetRemoved;
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public IWorkingSet[] getWorkingSets() {
    		SortedSet visibleSubset = new TreeSet(WorkingSetComparator.INSTANCE);
    		for (Iterator i = workingSets.iterator(); i.hasNext();) {
				IWorkingSet workingSet = (IWorkingSet) i.next();
				if (workingSet.isVisible()) {
					visibleSubset.add(workingSet);
				}
			}
        return (IWorkingSet[]) visibleSubset.toArray(new IWorkingSet[visibleSubset.size()]);
    }
    
    public IWorkingSet[] getAllWorkingSets() {
    		return (IWorkingSet[]) workingSets.toArray(new IWorkingSet[workingSets.size()]);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public IWorkingSet getWorkingSet(String name) {
        if (name == null || workingSets == null) {
			return null;
		}

        Iterator iter = workingSets.iterator();
        while (iter.hasNext()) {
            IWorkingSet workingSet = (IWorkingSet) iter.next();
            if (name.equals(workingSet.getName())) {
				return workingSet;
			}
        }
        return null;
    }
    
    // ---- recent working set management --------------------------------------
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public IWorkingSet[] getRecentWorkingSets() {
        return (IWorkingSet[]) recentWorkingSets.toArray(new IWorkingSet[recentWorkingSets.size()]);
    }

    /**
     * Adds the specified working set to the list of recently used
     * working sets.
     * 
     * @param workingSet working set to added to the list of recently 
     * 	used working sets.
     */
    protected void internalAddRecentWorkingSet(IWorkingSet workingSet) {
    		if (!workingSet.isVisible()) {
				return;
			}
        recentWorkingSets.remove(workingSet);
        recentWorkingSets.add(0, workingSet);
        if (recentWorkingSets.size() > MRU_SIZE) {
            recentWorkingSets.remove(MRU_SIZE);
        }
    }

    //---- equals and hash code -----------------------------------------------
    
    /**
     * Tests the receiver and the object for equality
     * 
     * @param object object to compare the receiver to
     * @return true=the object equals the receiver, it has the same 
     * 	working sets. false otherwise
     */
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!getClass().getName().equals(object.getClass().getName())) {
			return false;
		}
        AbstractWorkingSetManager other= (AbstractWorkingSetManager)object;
        return other.workingSets.equals(workingSets);
    }

    /**
     * Returns the hash code.
     * 
     * @return the hash code.
     */
    public int hashCode() {
        return workingSets.hashCode();
    }

    //---- property listeners -------------------------------------------------
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public void addPropertyChangeListener(IPropertyChangeListener listener) {
        addListenerObject(listener);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public void removePropertyChangeListener(IPropertyChangeListener listener) {
        removeListenerObject(listener);
    }

    /**
     * Notify property change listeners about a change to the list of 
     * working sets.
     * 
     * @param changeId one of 
     * 	IWorkingSetManager#CHANGE_WORKING_SET_ADD 
     * 	IWorkingSetManager#CHANGE_WORKING_SET_REMOVE
     * 	IWorkingSetManager#CHANGE_WORKING_SET_CONTENT_CHANGE 
     * 	IWorkingSetManager#CHANGE_WORKING_SET_NAME_CHANGE
     * @param oldValue the removed working set or null if a working set 
     * 	was added or changed.
     * @param newValue the new or changed working set or null if a working 
     * 	set was removed.
     */
    protected void firePropertyChange(String changeId, Object oldValue,
            Object newValue) {
        final Object[] listeners = getListeners();
		
        if (listeners.length == 0) {
			return;
		}
		
        final PropertyChangeEvent event = new PropertyChangeEvent(this,
                changeId, oldValue, newValue);
		Runnable notifier = new Runnable() {
			public void run() {
				for (int i = 0; i < listeners.length; i++) {
					final IPropertyChangeListener listener = (IPropertyChangeListener) listeners[i];
					ISafeRunnable safetyWrapper = new ISafeRunnable() {

						public void run() throws Exception {
							listener.propertyChange(event);
						}

						public void handleException(Throwable exception) {
							// logged by the runner
						}
					};
					SafeRunner.run(safetyWrapper);
				}
			}
		};
		// Notifications are sent on the UI thread.
		if (Display.getCurrent() != null) {
			notifier.run();
		} else {
			// Use an asyncExec to avoid deadlocks.
			Display.getDefault().asyncExec(notifier);
		}
    }
    
    /**
	 * Fires a property change event for the changed working set. Should only be
	 * called by org.eclipse.ui.internal.WorkingSet.
	 * 
	 * @param changedWorkingSet
	 *            the working set that has changed
	 * @param propertyChangeId
	 *            the changed property. one of
	 *            CHANGE_WORKING_SET_CONTENT_CHANGE,
	 *            CHANGE_WORKING_SET_LABEL_CHANGE, and
	 *            CHANGE_WORKING_SET_NAME_CHANGE
	 * @param oldValue
	 *            the old value
	 */
	public void workingSetChanged(IWorkingSet changedWorkingSet,
			String propertyChangeId, Object oldValue) {
		firePropertyChange(propertyChangeId, oldValue, changedWorkingSet);
	}
    
    // ---- Persistence
	// ----------------------------------------------------------------
    
    /**
     * Saves all persistable working sets in the persistence store.
     * 
     * @param memento the persistence store
     * @see IPersistableElement
     */
    public void saveWorkingSetState(IMemento memento) {
        Iterator iterator = workingSets.iterator();
        
        // break the sets into aggregates and non aggregates.  The aggregates should be saved after the non-aggregates
        // so that on restoration all necessary aggregate components can be found.
        
        ArrayList standardSets = new ArrayList();
        ArrayList aggregateSets = new ArrayList();
        while (iterator.hasNext()) {
        		IWorkingSet set = (IWorkingSet) iterator.next();
        		if (set instanceof AggregateWorkingSet) {
					aggregateSets.add(set);
				} else {
					standardSets.add(set);
				}
        }

        saveWorkingSetState(memento, standardSets);
        saveWorkingSetState(memento, aggregateSets);
    }

	/**
	 * @param memento the memento to save to
	 * @param list the working sets to save
	 * @since 3.2
	 */
	private void saveWorkingSetState(IMemento memento, List list) {
		for (Iterator i = list.iterator(); i.hasNext();) {
            IPersistableElement persistable = (IWorkingSet) i.next();
            IMemento workingSetMemento = memento
                    .createChild(IWorkbenchConstants.TAG_WORKING_SET);
            workingSetMemento.putString(IWorkbenchConstants.TAG_FACTORY_ID,
                    persistable.getFactoryId());
            persistable.saveState(workingSetMemento);
        }
	}
    
    /**
     * Recreates all working sets from the persistence store
     * and adds them to the receiver.
     * 
     * @param memento the persistence store
     */
    protected void restoreWorkingSetState(IMemento memento) {
        IMemento[] children = memento
                .getChildren(IWorkbenchConstants.TAG_WORKING_SET);
        for (int i = 0; i < children.length; i++) {
            IWorkingSet workingSet = restoreWorkingSet(children[i]);
            if (workingSet != null) {
            	internalAddWorkingSet(workingSet);
            }
        }
    }
    
    /**
     * Recreates a working set from the persistence store.
     * 
     * @param memento the persistence store
     * @return the working set created from the memento or null if
     * 	creation failed.
     */
    protected IWorkingSet restoreWorkingSet(IMemento memento) {
        String factoryID = memento
                .getString(IWorkbenchConstants.TAG_FACTORY_ID);

        if (factoryID == null) {
            // if the factory id was not set in the memento
            // then assume that the memento was created using
            // IMemento.saveState, and should be restored using WorkingSetFactory
            factoryID = AbstractWorkingSet.FACTORY_ID;
        }
        IElementFactory factory = PlatformUI.getWorkbench().getElementFactory(
                factoryID);
        if (factory == null) {
            WorkbenchPlugin
                    .log("Unable to restore working set - cannot instantiate factory: " + factoryID); //$NON-NLS-1$
            return null;
        }
        IAdaptable adaptable = factory.createElement(memento);
        if (adaptable == null) {
            WorkbenchPlugin
                    .log("Unable to restore working set - cannot instantiate working set: " + factoryID); //$NON-NLS-1$
            return null;
        }
        if ((adaptable instanceof IWorkingSet) == false) {
            WorkbenchPlugin
                    .log("Unable to restore working set - element is not an IWorkingSet: " + factoryID); //$NON-NLS-1$
            return null;
        }
        return (IWorkingSet) adaptable;
    }

    /**
     * Saves the list of most recently used working sets in the persistence 
     * store.
     * 
     * @param memento the persistence store
     */
    protected void saveMruList(IMemento memento) {
        Iterator iterator = recentWorkingSets.iterator();

        while (iterator.hasNext()) {
            IWorkingSet workingSet = (IWorkingSet) iterator.next();
            IMemento mruMemento = memento
                    .createChild(IWorkbenchConstants.TAG_MRU_LIST);

            mruMemento.putString(IWorkbenchConstants.TAG_NAME, workingSet
                    .getName());
        }
    }

   /**
     * Restores the list of most recently used working sets from the 
     * persistence store.
     * 
     * @param memento the persistence store
     */
    protected void restoreMruList(IMemento memento) {
        IMemento[] mruWorkingSets = memento
                .getChildren(IWorkbenchConstants.TAG_MRU_LIST);

        for (int i = mruWorkingSets.length - 1; i >= 0; i--) {
            String workingSetName = mruWorkingSets[i]
                    .getString(IWorkbenchConstants.TAG_NAME);
            if (workingSetName != null) {
                IWorkingSet workingSet = getWorkingSet(workingSetName);
                if (workingSet != null) {
                    internalAddRecentWorkingSet(workingSet);
                }
            }
        }
    }

    //---- user interface support -----------------------------------------------------
    
    /**
     * @see org.eclipse.ui.IWorkingSetManager#createWorkingSetEditWizard(org.eclipse.ui.IWorkingSet)
     * @since 2.1
     */
    public IWorkingSetEditWizard createWorkingSetEditWizard(
            IWorkingSet workingSet) {
        String editPageId = workingSet.getId();
        WorkingSetRegistry registry = WorkbenchPlugin.getDefault()
                .getWorkingSetRegistry();
        IWorkingSetPage editPage = null;

        if (editPageId != null) {
            editPage = registry.getWorkingSetPage(editPageId);
        }
 
        // the following block kind of defeats IWorkingSet.isEditable() and it
		// doesn't make sense for there to be a default page in such a case.
		
	     if (editPage == null) {
			editPage = registry.getDefaultWorkingSetPage();
			if (editPage == null) {
				return null;
			}
		}
		 
        WorkingSetEditWizard editWizard = new WorkingSetEditWizard(editPage);
        editWizard.setSelection(workingSet);
        return editWizard;
    }

    /**
     * @deprecated use createWorkingSetSelectionDialog(parent, true) instead
     */
    public IWorkingSetSelectionDialog createWorkingSetSelectionDialog(
            Shell parent) {
        return createWorkingSetSelectionDialog(parent, true);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager
     */
    public IWorkingSetSelectionDialog createWorkingSetSelectionDialog(
            Shell parent, boolean multi) {
        return createWorkingSetSelectionDialog(parent, multi, null);
    }
    
    /**
	 * {@inheritDoc}
	 */
    public IWorkingSetNewWizard createWorkingSetNewWizard(String[] workingSetIds) {
         WorkingSetDescriptor[] descriptors= getSupportedEditableDescriptors(workingSetIds);
         if (descriptors.length == 0) {
			return null;
		}
         return new WorkingSetNewWizard(descriptors);
}

    //---- working set delta handling -------------------------------------------------
    
	public void bundleChanged(BundleEvent event) {
		if (event.getBundle().getSymbolicName() == null)
			return;
		// If the workbench isn't running anymore simply return.
		if (!Workbench.getInstance().isRunning()) {
			return;
		}
		
		if (event.getBundle().getState() == Bundle.ACTIVE) {
			WorkingSetDescriptor[] descriptors = WorkbenchPlugin.getDefault()
					.getWorkingSetRegistry().getUpdaterDescriptorsForNamespace(
							event.getBundle().getSymbolicName());
			synchronized (updaters) {
				for (int i = 0; i < descriptors.length; i++) {
					WorkingSetDescriptor descriptor = descriptors[i];
					List workingSets = getWorkingSetsForId(descriptor.getId());
					if (workingSets.size() == 0) {
						continue;
					}
					IWorkingSetUpdater updater = getUpdater(descriptor);
					for (Iterator iter = workingSets.iterator(); iter.hasNext();) {
						IWorkingSet workingSet = (IWorkingSet) iter.next();
						if (!updater.contains(workingSet)) {
							updater.add(workingSet);
						}
					}
				}
			}
		}
	}

	private List getWorkingSetsForId(String id) {
		List result= new ArrayList();
    	for (Iterator iter= workingSets.iterator(); iter.hasNext();) {
    		IWorkingSet ws= (IWorkingSet)iter.next();
    		if (id.equals(ws.getId())) {
				result.add(ws);
			}
		}
    	return result;
	}
	
    private void addToUpdater(IWorkingSet workingSet) {
    	WorkingSetDescriptor descriptor= WorkbenchPlugin.getDefault()
			.getWorkingSetRegistry().getWorkingSetDescriptor(workingSet.getId());
    	if (descriptor == null || !descriptor.isUpdaterClassLoaded()) {
			return;
		}
		synchronized(updaters) {
	    	IWorkingSetUpdater updater= getUpdater(descriptor);
	    	if (!updater.contains(workingSet)) {
				updater.add(workingSet);
			}
		}
    }
    
    private IWorkingSetUpdater getUpdater(WorkingSetDescriptor descriptor) {
		IWorkingSetUpdater updater= (IWorkingSetUpdater)updaters.get(descriptor.getId());
    	if (updater == null) {
    		updater= descriptor.createWorkingSetUpdater();
    		if (updater == null) {
    			updater= NULL_UPDATER;
    		} else {
    			firePropertyChange(CHANGE_WORKING_SET_UPDATER_INSTALLED, null, updater);
    			PlatformUI.getWorkbench().getExtensionTracker().registerObject(
						descriptor.getConfigurationElement()
								.getDeclaringExtension(), updater,
						IExtensionTracker.REF_WEAK);
    			
    		}
        	updaters.put(descriptor.getId(), updater);
    	}
		return updater;
	}
    
    IWorkingSetElementAdapter getElementAdapter(WorkingSetDescriptor descriptor) {
		IWorkingSetElementAdapter elementAdapter = (IWorkingSetElementAdapter) elementAdapters
				.get(descriptor.getId());
		if (elementAdapter == null) {
			elementAdapter = descriptor.createWorkingSetElementAdapter();
			if (elementAdapter == null) {
				elementAdapter = IDENTITY_ADAPTER;
			} else {
				elementAdapters.put(descriptor.getId(), elementAdapter);
			}
		}
		return elementAdapter;
	}

	private void removeFromUpdater(IWorkingSet workingSet) {
		synchronized (updaters) {
			IWorkingSetUpdater updater = (IWorkingSetUpdater) updaters
					.get(workingSet.getId());
			if (updater != null) {
				updater.remove(workingSet);
			}
		}
    }	
    
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkingSetManager#createWorkingSetSelectionDialog(org.eclipse.swt.widgets.Shell, boolean, java.lang.String[])
     */
    public IWorkingSetSelectionDialog createWorkingSetSelectionDialog(Shell parent, boolean multi, String[] workingsSetIds) {
        return new WorkingSetSelectionDialog(parent, multi, workingsSetIds);
    }

	/**
	 * Save the state to the state file.
	 * 
	 * @param stateFile
	 * @throws IOException
	 */
	public void saveState(File stateFile) throws IOException {
		XMLMemento memento = XMLMemento
				.createWriteRoot(IWorkbenchConstants.TAG_WORKING_SET_MANAGER);
		saveWorkingSetState(memento);
		saveMruList(memento);
	
		FileOutputStream stream = new FileOutputStream(stateFile);
		OutputStreamWriter writer = new OutputStreamWriter(stream, "utf-8"); //$NON-NLS-1$
		memento.save(writer);
		writer.close();
	
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler#addExtension(org.eclipse.core.runtime.dynamichelpers.IExtensionTracker, org.eclipse.core.runtime.IExtension)
	 */
	public void addExtension(IExtensionTracker tracker, IExtension extension) {
		// nothing - this is handled lazily.  These items are only created as needed by the getUpdater() and getElementAdapter() methods
		
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension, java.lang.Object[])
	 */
	public void removeExtension(IExtension extension, Object[] objects) {
		for (int i = 0; i < objects.length; i++) {
			Object object = objects[i];
			if (object instanceof IWorkingSetUpdater) {
				removeUpdater((IWorkingSetUpdater)object);
				
			}
			if (object instanceof IWorkingSetElementAdapter) {
				removeElementAdapter((IWorkingSetElementAdapter) object);
			}
		} 
	}

	/**
	 * Remove the element adapter from the manager and dispose of it.
	 * 
	 * @param elementAdapter
	 * @since 3.3
	 */
	private void removeElementAdapter(
			final IWorkingSetElementAdapter elementAdapter) {
		SafeRunner.run(new ISafeRunnable() {

			public void handleException(Throwable exception) {
				StatusManager.getManager().handle(
						StatusUtil.newStatus(PlatformUI.PLUGIN_ID, exception));
			}

			public void run() throws Exception {
				elementAdapter.dispose();

			}
		});
		synchronized (elementAdapters) {
			elementAdapters.values().remove(elementAdapter);
		}
	}

	/**
	 * Remove the updater from the manager and dispose of it.
	 * 
	 * @param updater
	 * @since 3.3
	 */
	private void removeUpdater(final IWorkingSetUpdater updater) {
		SafeRunner.run(new ISafeRunnable() {

			public void handleException(Throwable exception) {
				StatusManager.getManager().handle(
						StatusUtil.newStatus(PlatformUI.PLUGIN_ID, exception));
			}

			public void run() throws Exception {
				updater.dispose();

			}
		});
		synchronized (updaters) {
			updaters.values().remove(updater);			
		}
		firePropertyChange(IWorkingSetManager.CHANGE_WORKING_SET_UPDATER_UNINSTALLED, updater, null);
	}
}
