content: /*******************************************************************************
   * Copyright (c) 2000, 2008 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.wst.jsdt.internal.ui.workingsets;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.IdentityHashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.IAdaptable;
  import org.eclipse.core.runtime.ListenerList;
  import org.eclipse.jface.util.IPropertyChangeListener;
  import org.eclipse.jface.util.PropertyChangeEvent;
  import org.eclipse.jface.viewers.IElementComparer;
  import org.eclipse.ui.ILocalWorkingSetManager;
  import org.eclipse.ui.IMemento;
  import org.eclipse.ui.IWorkingSet;
  import org.eclipse.ui.IWorkingSetManager;
  import org.eclipse.ui.IWorkingSetUpdater;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  
  public class WorkingSetModel {
  
  	public static final String CHANGE_WORKING_SET_MODEL_CONTENT= "workingSetModelChanged"; //$NON-NLS-1$
  
  	public static final IElementComparer COMPARER= new WorkingSetComparar();
  
  	private static final String TAG_LOCAL_WORKING_SET_MANAGER= "localWorkingSetManager"; //$NON-NLS-1$
  	private static final String TAG_ACTIVE_WORKING_SET= "activeWorkingSet"; //$NON-NLS-1$
  	private static final String TAG_WORKING_SET_NAME= "workingSetName"; //$NON-NLS-1$
  	private static final String TAG_CONFIGURED= "configured"; //$NON-NLS-1$
  
  	private ILocalWorkingSetManager fLocalWorkingSetManager;
  	private List fActiveWorkingSets;
  	private ListenerList fListeners;
  	private IPropertyChangeListener fWorkingSetManagerListener;
  	private OthersWorkingSetUpdater fOthersWorkingSetUpdater;
  
  	private ElementMapper fElementMapper= new ElementMapper();
  
  	private boolean fConfigured;
  
  	private static class WorkingSetComparar implements IElementComparer {
  		public boolean equals(Object o1, Object o2) {
  			IWorkingSet w1= o1 instanceof IWorkingSet ? (IWorkingSet)o1 : null;
  			IWorkingSet w2= o2 instanceof IWorkingSet ? (IWorkingSet)o2 : null;
  			if (w1 == null || w2 == null)
  				return o1.equals(o2);
  			return w1 == w2;
  		}
  		public int hashCode(Object element) {
  			if (element instanceof IWorkingSet)
  				return System.identityHashCode(element);
  			return element.hashCode();
  		}
  	}
  
  	private static class ElementMapper {
  		private Map fElementToWorkingSet= new HashMap();
  		private Map fWorkingSetToElement= new IdentityHashMap();
  
  		private Map fResourceToWorkingSet= new HashMap();
  		private List fNonProjectTopLevelElements= new ArrayList();
  
  		public void clear() {
  			fElementToWorkingSet.clear();
  			fWorkingSetToElement.clear();
  			fResourceToWorkingSet.clear();
  			fNonProjectTopLevelElements.clear();
  		}
  		public void rebuild(IWorkingSet[] workingSets) {
  			clear();
  			for (int i= 0; i < workingSets.length; i++) {
  				put(workingSets[i]);
  			}
  		}
  		public IAdaptable[] remove(IWorkingSet ws) {
  			IAdaptable[] elements= (IAdaptable[])fWorkingSetToElement.remove(ws);
  			if (elements != null) {
  				for (int i= 0; i < elements.length; i++) {
  					removeElement(elements[i], ws);
  				}
  			}
  			return elements;
  		}
  		public IAdaptable[] refresh(IWorkingSet ws) {
  			IAdaptable[] oldElements= (IAdaptable[])fWorkingSetToElement.get(ws);
  			if (oldElements == null)
  				return null;
  			IAdaptable[] newElements= ws.getElements();
  			List toRemove= new ArrayList(Arrays.asList(oldElements));
  			List toAdd= new ArrayList(Arrays.asList(newElements));
  			computeDelta(toRemove, toAdd, oldElements, newElements);
  			for (Iterator iter= toAdd.iterator(); iter.hasNext();) {
  				addElement((IAdaptable)iter.next(), ws);
  			}
  			for (Iterator iter= toRemove.iterator(); iter.hasNext();) {
  				removeElement((IAdaptable)iter.next(), ws);
  			}
  			if (toRemove.size() > 0 || toAdd.size() > 0)
  				fWorkingSetToElement.put(ws, newElements);
  			return oldElements;
  		}
  		private void computeDelta(List toRemove, List toAdd, IAdaptable[] oldElements, IAdaptable[] newElements) {
  			for (int i= 0; i < oldElements.length; i++) {
  				toAdd.remove(oldElements[i]);
  			}
  			for (int i= 0; i < newElements.length; i++) {
  				toRemove.remove(newElements[i]);
  			}
  
  		}
  		public IWorkingSet getFirstWorkingSet(Object element) {
  			return (IWorkingSet)getFirstElement(fElementToWorkingSet, element);
  		}
  		public List getAllWorkingSets(Object element) {
  			 List allElements= getAllElements(fElementToWorkingSet, element);
  			 if (allElements.isEmpty() && element instanceof IJavaScriptElement) {
  				 // try a second time in case the working set was manually updated (bug 168032)
  				 allElements= getAllElements(fElementToWorkingSet, ((IJavaScriptElement) element).getResource());
  			 }
  			 return allElements;
  		}
  		public IWorkingSet getFirstWorkingSetForResource(IResource resource) {
  			return (IWorkingSet)getFirstElement(fResourceToWorkingSet, resource);
  		}
  		public List getAllWorkingSetsForResource(IResource resource) {
  			return getAllElements(fResourceToWorkingSet, resource);
  		}
  		public List getNonProjectTopLevelElements() {
  			return fNonProjectTopLevelElements;
  		}
  		private void put(IWorkingSet ws) {
  			if (fWorkingSetToElement.containsKey(ws))
  				return;
  			IAdaptable[] elements= ws.getElements();
  			fWorkingSetToElement.put(ws, elements);
  			for (int i= 0; i < elements.length; i++) {
  				IAdaptable element= elements[i];
  				addElement(element, ws);
  				if (!(element instanceof IProject) && !(element instanceof IJavaScriptProject)) {
  					fNonProjectTopLevelElements.add(element);
  				}
  			}
  		}
  		private void addElement(IAdaptable element, IWorkingSet ws) {
  			addToMap(fElementToWorkingSet, element, ws);
  			IResource resource= (IResource)element.getAdapter(IResource.class);
  			if (resource != null) {
  				addToMap(fResourceToWorkingSet, resource, ws);
  			}
  		}
  		private void removeElement(IAdaptable element, IWorkingSet ws) {
  			removeFromMap(fElementToWorkingSet, element, ws);
  			IResource resource= (IResource)element.getAdapter(IResource.class);
  			if (resource != null) {
  				removeFromMap(fResourceToWorkingSet, resource, ws);
  			}
  		}
  		private void addToMap(Map map, IAdaptable key, IWorkingSet value) {
  			Object obj= map.get(key);
  			if (obj == null) {
  				map.put(key, value);
  			} else if (obj instanceof IWorkingSet) {
  				List l= new ArrayList(2);
  				l.add(obj);
  				l.add(value);
  				map.put(key, l);
  			} else if (obj instanceof List) {
  				((List)obj).add(value);
  			}
  		}
  		private void removeFromMap(Map map, IAdaptable key, IWorkingSet value) {
  			Object current= map.get(key);
  			if (current == null) {
  				return;
  			} else if (current instanceof List) {
  				List list= (List)current;
  				list.remove(value);
  				switch (list.size()) {
  					case 0:
  						map.remove(key);
  						break;
  					case 1:
  						map.put(key, list.get(0));
  						break;
  				}
  			} else if (current == value) {
  				map.remove(key);
  			}
  		}
  		private Object getFirstElement(Map map, Object key) {
  			Object obj= map.get(key);
  			if (obj instanceof List) 
  				return ((List)obj).get(0);
  			return obj;
  		}
  		private List getAllElements(Map map, Object key) {
  			Object obj= map.get(key);
  			if (obj instanceof List)
  				return (List)obj;
  			if (obj == null)
  				return Collections.EMPTY_LIST;
  			List result= new ArrayList(1);
  			result.add(obj);
  			return result;
  		}
  	}
  
  	/**
  	 * @param memento a memento, or <code>null</code>
  	 */
  	public WorkingSetModel(IMemento memento) {
  		fLocalWorkingSetManager= PlatformUI.getWorkbench().createLocalWorkingSetManager();
  		addListenersToWorkingSetManagers();
  		fActiveWorkingSets= new ArrayList(2);
  		
  		if (memento == null || ! restoreState(memento)) {
  			IWorkingSet others= fLocalWorkingSetManager.createWorkingSet(WorkingSetMessages.WorkingSetModel_others_name, new IAdaptable[0]); 
  			others.setId(OthersWorkingSetUpdater.ID);
  			fLocalWorkingSetManager.addWorkingSet(others);
  			fActiveWorkingSets.add(others);
  		}
  		Assert.isNotNull(fOthersWorkingSetUpdater);
  		
  		fElementMapper.rebuild(getActiveWorkingSets());
  		fOthersWorkingSetUpdater.updateElements();
  	}
  
  	private void addListenersToWorkingSetManagers() {
  		fListeners= new ListenerList(ListenerList.IDENTITY);
  		fWorkingSetManagerListener= new IPropertyChangeListener() {
  			public void propertyChange(PropertyChangeEvent event) {
  				workingSetManagerChanged(event);
  			}
  		};
  		PlatformUI.getWorkbench().getWorkingSetManager().addPropertyChangeListener(fWorkingSetManagerListener);
  		fLocalWorkingSetManager.addPropertyChangeListener(fWorkingSetManagerListener);
  	}
  
  	public void dispose() {
  		if (fWorkingSetManagerListener != null) {
  			PlatformUI.getWorkbench().getWorkingSetManager().removePropertyChangeListener(fWorkingSetManagerListener);
  			fLocalWorkingSetManager.removePropertyChangeListener(fWorkingSetManagerListener);
  			fLocalWorkingSetManager.dispose();
  			fWorkingSetManagerListener= null;
  		}
  	}
  
  	//---- model relationships ---------------------------------------
  
  	public IAdaptable[] getChildren(IWorkingSet workingSet) {
  		return workingSet.getElements();
  	}
  
  	public Object getParent(Object element) {
  		if (element instanceof IWorkingSet && fActiveWorkingSets.contains(element))
  			return this;
  		return fElementMapper.getFirstWorkingSet(element);
  	}
  
  	public Object[] getAllParents(Object element) {
  		if (element instanceof IWorkingSet && fActiveWorkingSets.contains(element))
  			return new Object[] {this};
  		return fElementMapper.getAllWorkingSets(element).toArray();
  	}
  
  	public Object[] addWorkingSets(Object[] elements) {
  		List result= null;
  		for (int i= 0; i < elements.length; i++) {
  			Object element= elements[i];
  			List sets= null;
  			if (element instanceof IResource) {
  				sets= fElementMapper.getAllWorkingSetsForResource((IResource)element);
  			} else {
  				sets= fElementMapper.getAllWorkingSets(element);
  			}
  			if (sets != null && sets.size() > 0) {
  				if (result == null)
  					result= new ArrayList(Arrays.asList(elements));
  				result.addAll(sets);
  			}
  		}
  		if (result == null)
  			return elements;
  		return result.toArray();
  	}
  
  	public boolean needsConfiguration() {
  		return !fConfigured && fActiveWorkingSets.size() == 1 &&
  		OthersWorkingSetUpdater.ID.equals(((IWorkingSet)fActiveWorkingSets.get(0)).getId());
  	}
  
  	public void configured() {
  		fConfigured= true;
  	}
  
  	//---- working set management -----------------------------------
  
  	/**
  	 * Adds a property change listener.
  	 * 
  	 * @param listener the property change listener to add
  	 */
  	public void addPropertyChangeListener(IPropertyChangeListener listener) {
  		fListeners.add(listener);
  	}
  
  	/**
  	 * Removes the property change listener.
  	 * 
  	 * @param listener the property change listener to remove
  	 */
  	public void removePropertyChangeListener(IPropertyChangeListener listener) {
  		fListeners.remove(listener);
  	}
  
  	public IWorkingSet[] getActiveWorkingSets() {
  		return (IWorkingSet[])fActiveWorkingSets.toArray(new IWorkingSet[fActiveWorkingSets.size()]);
  	}
  
  	public IWorkingSet[] getAllWorkingSets() {
  		List result= new ArrayList();
  		result.addAll(fActiveWorkingSets);
  		IWorkingSet[] locals= fLocalWorkingSetManager.getWorkingSets();
  		for (int i= 0; i < locals.length; i++) {
  			if (!result.contains(locals[i]))
  				result.add(locals[i]);
  		}
  		IWorkingSet[] globals= PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSets();
  		for (int i= 0; i < globals.length; i++) {
  			if (!result.contains(globals[i]))
  				result.add(globals[i]);
  		}
  		return (IWorkingSet[])result.toArray(new IWorkingSet[result.size()]);
  	}
  
  	public void setActiveWorkingSets(IWorkingSet[] workingSets) {
  		fActiveWorkingSets= new ArrayList(Arrays.asList(workingSets));
  		fElementMapper.rebuild(getActiveWorkingSets());
  		fOthersWorkingSetUpdater.updateElements();
  		fireEvent(new PropertyChangeEvent(this, CHANGE_WORKING_SET_MODEL_CONTENT, null, null));
  	}
  
  	public void saveState(IMemento memento) {
  		memento.putString(TAG_CONFIGURED, Boolean.toString(fConfigured));
  		fLocalWorkingSetManager.saveState(memento.createChild(TAG_LOCAL_WORKING_SET_MANAGER));
  		for (Iterator iter= fActiveWorkingSets.iterator(); iter.hasNext();) {
  			IMemento active= memento.createChild(TAG_ACTIVE_WORKING_SET);
  			IWorkingSet workingSet= (IWorkingSet)iter.next();
  			active.putString(TAG_WORKING_SET_NAME, workingSet.getName());
  		}
  	}
  	
  	public List getNonProjectTopLevelElements() {
  		return fElementMapper.getNonProjectTopLevelElements();
  	}
  
  	/**
  	 * Restore localWorkingSetManager and active working sets
  	 * @param memento
  	 * @return whether the restore was successful
  	 */
  	private boolean restoreState(IMemento memento) {
  		String configured= memento.getString(TAG_CONFIGURED);
  		if (configured == null)
  			return false;
  		
  		fConfigured= Boolean.valueOf(configured).booleanValue();
  		fLocalWorkingSetManager.restoreState(memento.getChild(TAG_LOCAL_WORKING_SET_MANAGER));
  
  		IMemento[] actives= memento.getChildren(TAG_ACTIVE_WORKING_SET);
  		for (int i= 0; i < actives.length; i++) {
  			String name= actives[i].getString(TAG_WORKING_SET_NAME);
  			if (name != null) {
  				IWorkingSet ws= fLocalWorkingSetManager.getWorkingSet(name);
  				if (ws == null) {
  					ws= PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSet(name);
  				}
  				if (ws != null) {
  					fActiveWorkingSets.add(ws);
  				}
  			}
  		}
  		return true;
  	}
  	private void workingSetManagerChanged(PropertyChangeEvent event) {
  		String property= event.getProperty();
  		if (IWorkingSetManager.CHANGE_WORKING_SET_UPDATER_INSTALLED.equals(property) && event.getSource() == fLocalWorkingSetManager) {
  			IWorkingSetUpdater updater= (IWorkingSetUpdater)event.getNewValue();
  			if (updater instanceof OthersWorkingSetUpdater) {
  				fOthersWorkingSetUpdater= (OthersWorkingSetUpdater)updater;
  				fOthersWorkingSetUpdater.init(this);
  			}
  			return;
  		}
  		// don't handle working sets not managed by the model
  		if (!isAffected(event))
  			return;
  
  		if (IWorkingSetManager.CHANGE_WORKING_SET_CONTENT_CHANGE.equals(property)) {
  			IWorkingSet workingSet= (IWorkingSet)event.getNewValue();
  			IAdaptable[] elements= fElementMapper.refresh(workingSet);
  			if (elements != null) {
  				fireEvent(event);
  			}
  		} else if (IWorkingSetManager.CHANGE_WORKING_SET_REMOVE.equals(property)) {
  			IWorkingSet workingSet= (IWorkingSet)event.getOldValue();
  			List elements= new ArrayList(fActiveWorkingSets);
  			elements.remove(workingSet);
  			setActiveWorkingSets((IWorkingSet[])elements.toArray(new IWorkingSet[elements.size()]));
  		} else if (IWorkingSetManager.CHANGE_WORKING_SET_NAME_CHANGE.equals(property)) {
  			fireEvent(event);
  		}
  	}
  
  	private void fireEvent(PropertyChangeEvent event) {
  		Object[] listeners= fListeners.getListeners();
  		for (int i= 0; i < listeners.length; i++) {
  			((IPropertyChangeListener)listeners[i]).propertyChange(event);
  		}
  	}
  
  	private boolean isAffected(PropertyChangeEvent event) {
  		if (fActiveWorkingSets == null)
  			return false;
  		Object oldValue= event.getOldValue();
  		Object newValue= event.getNewValue();
  		if ((oldValue != null && fActiveWorkingSets.contains(oldValue)) 
  				|| (newValue != null && fActiveWorkingSets.contains(newValue))) {
  			return true;
  		}
  		return false;
  	}
  
  	public boolean isActiveWorkingSet(IWorkingSet changedWorkingSet) {
  		return fActiveWorkingSets.contains(changedWorkingSet);
  	}
  }
  