| /******************************************************************************* |
| * Copyright (c) 2006, 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.navigator; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.ITreePathContentProvider; |
| import org.eclipse.jface.viewers.ITreeSelection; |
| import org.eclipse.jface.viewers.StructuredViewer; |
| import org.eclipse.jface.viewers.TreePath; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.ISaveablesLifecycleListener; |
| import org.eclipse.ui.ISaveablesSource; |
| import org.eclipse.ui.Saveable; |
| import org.eclipse.ui.SaveablesLifecycleEvent; |
| import org.eclipse.ui.internal.navigator.VisibilityAssistant.VisibilityListener; |
| import org.eclipse.ui.internal.navigator.extensions.ExtensionPriorityComparator; |
| import org.eclipse.ui.internal.navigator.extensions.NavigatorContentDescriptor; |
| import org.eclipse.ui.internal.navigator.extensions.NavigatorContentExtension; |
| import org.eclipse.ui.navigator.INavigatorContentDescriptor; |
| import org.eclipse.ui.navigator.INavigatorSaveablesService; |
| import org.eclipse.ui.navigator.SaveablesProvider; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleEvent; |
| |
| /** |
| * Implementation of INavigatorSaveablesService. |
| * <p> |
| * Implementation note: all externally callable methods are synchronized. The |
| * private helper methods are not synchronized since they can only be called |
| * from methods that already hold the lock. |
| * </p> |
| * @since 3.2 |
| * |
| */ |
| public class NavigatorSaveablesService implements INavigatorSaveablesService, VisibilityListener { |
| |
| private NavigatorContentService contentService; |
| |
| private static List instances = new ArrayList(); |
| |
| /** |
| * @param contentService |
| */ |
| public NavigatorSaveablesService(NavigatorContentService contentService) { |
| this.contentService = contentService; |
| } |
| |
| private static void addInstance(NavigatorSaveablesService saveablesService) { |
| synchronized (instances) { |
| instances.add(saveablesService); |
| } |
| } |
| |
| private static void removeInstance( |
| NavigatorSaveablesService saveablesService) { |
| synchronized (instances) { |
| instances.remove(saveablesService); |
| } |
| } |
| |
| /** |
| * @param event |
| */ |
| /* package */ static void bundleChanged(BundleEvent event) { |
| synchronized(instances) { |
| if (event.getType() == BundleEvent.STARTED) { |
| // System.out.println("bundle started: " + event.getBundle().getSymbolicName()); //$NON-NLS-1$ |
| for (Iterator it = instances.iterator(); it.hasNext();) { |
| NavigatorSaveablesService instance = (NavigatorSaveablesService) it |
| .next(); |
| instance.handleBundleStarted(event.getBundle() |
| .getSymbolicName()); |
| } |
| } else if (event.getType() == BundleEvent.STOPPED) { |
| // System.out.println("bundle stopped: " + event.getBundle().getSymbolicName()); //$NON-NLS-1$ |
| for (Iterator it = instances.iterator(); it.hasNext();) { |
| NavigatorSaveablesService instance = (NavigatorSaveablesService) it |
| .next(); |
| instance.handleBundleStopped(event.getBundle() |
| .getSymbolicName()); |
| } |
| } |
| } |
| } |
| |
| private class LifecycleListener implements ISaveablesLifecycleListener { |
| public void handleLifecycleEvent(SaveablesLifecycleEvent event) { |
| Saveable[] saveables = event.getSaveables(); |
| switch (event.getEventType()) { |
| case SaveablesLifecycleEvent.POST_OPEN: |
| recomputeSaveablesAndNotify(false, null); |
| break; |
| case SaveablesLifecycleEvent.POST_CLOSE: |
| recomputeSaveablesAndNotify(false, null); |
| break; |
| case SaveablesLifecycleEvent.DIRTY_CHANGED: |
| Saveable[] shownSaveables = getShownSaveables(saveables); |
| if (shownSaveables.length > 0) { |
| outsideListener |
| .handleLifecycleEvent(new SaveablesLifecycleEvent( |
| saveablesSource, |
| SaveablesLifecycleEvent.DIRTY_CHANGED, |
| shownSaveables, false)); |
| } |
| break; |
| } |
| } |
| } |
| |
| private Saveable[] currentSaveables; |
| |
| private ISaveablesLifecycleListener outsideListener; |
| |
| private ISaveablesLifecycleListener saveablesLifecycleListener = new LifecycleListener(); |
| |
| private ISaveablesSource saveablesSource; |
| |
| private StructuredViewer viewer; |
| |
| private SaveablesProvider[] saveablesProviders; |
| |
| private DisposeListener disposeListener = new DisposeListener() { |
| |
| public void widgetDisposed(DisposeEvent e) { |
| // synchronize in the same order as in the init method. |
| synchronized (instances) { |
| synchronized (NavigatorSaveablesService.this) { |
| if (saveablesProviders != null) { |
| for (int i = 0; i < saveablesProviders.length; i++) { |
| saveablesProviders[i].dispose(); |
| } |
| } |
| removeInstance(NavigatorSaveablesService.this); |
| contentService = null; |
| currentSaveables = null; |
| outsideListener = null; |
| saveablesLifecycleListener = null; |
| saveablesSource = null; |
| viewer = null; |
| saveablesProviders = null; |
| disposeListener = null; |
| } |
| } |
| } |
| }; |
| |
| private Map inactivePluginsWithSaveablesProviders; |
| |
| /** |
| * a TreeMap (NavigatorContentDescriptor->SaveablesProvider) which uses |
| * ExtensionPriorityComparator.INSTANCE as its Comparator |
| */ |
| private Map saveablesProviderMap; |
| |
| /** |
| * Implementation note: This is not synchronized at the method level because it needs to |
| * synchronize on "instances" first, then on "this", to avoid potential deadlock. |
| * |
| * @param saveablesSource |
| * @param viewer |
| * @param outsideListener |
| * |
| */ |
| public void init(final ISaveablesSource saveablesSource, |
| final StructuredViewer viewer, |
| ISaveablesLifecycleListener outsideListener) { |
| // Synchronize on instances to make sure that we don't miss bundle started events. |
| synchronized (instances) { |
| // Synchronize on this because we are calling computeSaveables. |
| // Synchronization must remain in this order to avoid deadlock. |
| // This might not be necessary because at this time, no other |
| // concurrent calls should be possible, but it doesn't hurt either. |
| // For example, the initialization sequence might change in the |
| // future. |
| synchronized (this) { |
| this.saveablesSource = saveablesSource; |
| this.viewer = viewer; |
| this.outsideListener = outsideListener; |
| currentSaveables = computeSaveables(); |
| // add this instance after we are fully inialized. |
| addInstance(this); |
| } |
| } |
| viewer.getControl().addDisposeListener(disposeListener); |
| } |
| |
| /** helper to compute the saveables for which elements are part of the tree. |
| * Must be called from a synchronized method. |
| * |
| * @return the saveables |
| */ |
| private Saveable[] computeSaveables() { |
| ITreeContentProvider contentProvider = (ITreeContentProvider) viewer |
| .getContentProvider(); |
| boolean isTreepathContentProvider = contentProvider instanceof ITreePathContentProvider; |
| Object viewerInput = viewer.getInput(); |
| List result = new ArrayList(); |
| Set roots = new HashSet(Arrays.asList(contentProvider |
| .getElements(viewerInput))); |
| SaveablesProvider[] saveablesProviders = getSaveablesProviders(); |
| for (int i = 0; i < saveablesProviders.length; i++) { |
| SaveablesProvider saveablesProvider = saveablesProviders[i]; |
| Saveable[] saveables = saveablesProvider.getSaveables(); |
| for (int j = 0; j < saveables.length; j++) { |
| Saveable saveable = saveables[j]; |
| Object[] elements = saveablesProvider.getElements(saveable); |
| // the saveable is added to the result if at least one of the |
| // elements representing the saveable appears in the tree, i.e. |
| // if its parent chain leads to a root node. |
| boolean foundRoot = false; |
| for (int k = 0; !foundRoot && k < elements.length; k++) { |
| Object element = elements[k]; |
| if (roots.contains(element)) { |
| result.add(saveable); |
| foundRoot = true; |
| } else if (isTreepathContentProvider) { |
| ITreePathContentProvider treePathContentProvider = (ITreePathContentProvider) contentProvider; |
| TreePath[] parentPaths = treePathContentProvider.getParents(element); |
| for (int l = 0; !foundRoot && l < parentPaths.length; l++) { |
| TreePath parentPath = parentPaths[l]; |
| for (int m = 0; !foundRoot && m < parentPath.getSegmentCount(); m++) { |
| if (roots.contains(parentPath.getSegment(m))) { |
| result.add(saveable); |
| foundRoot = true; |
| } |
| } |
| } |
| } else { |
| while (!foundRoot && element != null) { |
| if (roots.contains(element)) { |
| // found a parent chain leading to a root. The |
| // saveable is part of the tree. |
| result.add(saveable); |
| foundRoot = true; |
| } else { |
| element = contentProvider.getParent(element); |
| } |
| } |
| } |
| } |
| } |
| } |
| return (Saveable[]) result.toArray(new Saveable[result.size()]); |
| } |
| |
| public synchronized Saveable[] getActiveSaveables() { |
| ITreeContentProvider contentProvider = (ITreeContentProvider) viewer |
| .getContentProvider(); |
| IStructuredSelection selection = (IStructuredSelection) viewer |
| .getSelection(); |
| if (selection instanceof ITreeSelection) { |
| return getActiveSaveablesFromTreeSelection((ITreeSelection) selection); |
| } else if (contentProvider instanceof ITreePathContentProvider) { |
| return getActiveSaveablesFromTreePathProvider(selection, (ITreePathContentProvider) contentProvider); |
| } else { |
| return getActiveSaveablesFromTreeProvider(selection, contentProvider); |
| } |
| } |
| |
| /** |
| * @param selection |
| * @return the active saveables |
| */ |
| private Saveable[] getActiveSaveablesFromTreeSelection( |
| ITreeSelection selection) { |
| Set result = new HashSet(); |
| TreePath[] paths = selection.getPaths(); |
| for (int i = 0; i < paths.length; i++) { |
| TreePath path = paths[i]; |
| Saveable saveable = findSaveable(path); |
| if (saveable != null) { |
| result.add(saveable); |
| } |
| } |
| return (Saveable[]) result.toArray(new Saveable[result.size()]); |
| } |
| |
| /** |
| * @param selection |
| * @param provider |
| * @return the active saveables |
| */ |
| private Saveable[] getActiveSaveablesFromTreePathProvider( |
| IStructuredSelection selection, ITreePathContentProvider provider) { |
| Set result = new HashSet(); |
| for (Iterator it = selection.iterator(); it.hasNext();) { |
| Object element = it.next(); |
| Saveable saveable = getSaveable(element); |
| if (saveable != null) { |
| result.add(saveable); |
| } else { |
| TreePath[] paths = provider.getParents(element); |
| saveable = findSaveable(paths); |
| if (saveable != null) { |
| result.add(saveable); |
| } |
| } |
| } |
| return (Saveable[]) result.toArray(new Saveable[result.size()]); |
| } |
| |
| /** |
| * @param selection |
| * @param contentProvider |
| * @return the active saveables |
| */ |
| private Saveable[] getActiveSaveablesFromTreeProvider( |
| IStructuredSelection selection, ITreeContentProvider contentProvider) { |
| Set result = new HashSet(); |
| for (Iterator it = selection.iterator(); it.hasNext();) { |
| Object element = it.next(); |
| Saveable saveable = findSaveable(element, contentProvider); |
| if (saveable != null) { |
| result.add(saveable); |
| } |
| } |
| return (Saveable[]) result.toArray(new Saveable[result.size()]); |
| } |
| |
| /** |
| * @param element |
| * @param contentProvider |
| * @return the saveable, or null |
| */ |
| private Saveable findSaveable(Object element, |
| ITreeContentProvider contentProvider) { |
| while (element != null) { |
| Saveable saveable = getSaveable(element); |
| if (saveable != null) { |
| return saveable; |
| } |
| element = contentProvider.getParent(element); |
| } |
| return null; |
| } |
| |
| /** |
| * @param paths |
| * @return the saveable, or null |
| */ |
| private Saveable findSaveable(TreePath[] paths) { |
| for (int i = 0; i < paths.length; i++) { |
| Saveable saveable = findSaveable(paths[i]); |
| if (saveable != null) { |
| return saveable; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param path |
| * @return a saveable, or null |
| */ |
| private Saveable findSaveable(TreePath path) { |
| int count = path.getSegmentCount(); |
| for (int j = count - 1; j >= 0; j--) { |
| Object parent = path.getSegment(j); |
| Saveable saveable = getSaveable(parent); |
| if (saveable != null) { |
| return saveable; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param element |
| * @return the saveable associated with the given element |
| */ |
| private Saveable getSaveable(Object element) { |
| if (saveablesProviderMap==null) { |
| // has the side effect of recomputing saveablesProviderMap: |
| getSaveablesProviders(); |
| } |
| for(Iterator sItr = saveablesProviderMap.keySet().iterator(); sItr.hasNext();) { |
| NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) sItr.next(); |
| if(descriptor.isTriggerPoint(element) || descriptor.isPossibleChild(element)) { |
| SaveablesProvider provider = (SaveablesProvider) saveablesProviderMap.get(descriptor); |
| Saveable saveable = provider.getSaveable(element); |
| if(saveable != null) { |
| return saveable; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @return the saveables |
| */ |
| public synchronized Saveable[] getSaveables() { |
| return currentSaveables; |
| } |
| |
| /** |
| * @return all SaveablesProvider objects |
| */ |
| private SaveablesProvider[] getSaveablesProviders() { |
| // TODO optimize this |
| if (saveablesProviders == null) { |
| inactivePluginsWithSaveablesProviders = new HashMap(); |
| saveablesProviderMap = new TreeMap(ExtensionPriorityComparator.INSTANCE); |
| INavigatorContentDescriptor[] descriptors = contentService |
| .getActiveDescriptorsWithSaveables(); |
| List result = new ArrayList(); |
| for (int i = 0; i < descriptors.length; i++) { |
| NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) descriptors[i]; |
| String pluginId = ((NavigatorContentDescriptor) descriptor) |
| .getContribution().getPluginId(); |
| if (Platform.getBundle(pluginId).getState() != Bundle.ACTIVE) { |
| List inactiveDescriptors = (List) inactivePluginsWithSaveablesProviders |
| .get(pluginId); |
| if (inactiveDescriptors == null) { |
| inactiveDescriptors = new ArrayList(); |
| inactivePluginsWithSaveablesProviders.put(pluginId, |
| inactiveDescriptors); |
| } |
| inactiveDescriptors.add(descriptor); |
| } else { |
| SaveablesProvider saveablesProvider = createSaveablesProvider(descriptor); |
| if (saveablesProvider != null) { |
| saveablesProvider.init(saveablesLifecycleListener); |
| result.add(saveablesProvider); |
| saveablesProviderMap.put(descriptor, saveablesProvider); |
| } |
| } |
| } |
| saveablesProviders = (SaveablesProvider[]) result |
| .toArray(new SaveablesProvider[result.size()]); |
| } |
| return saveablesProviders; |
| } |
| |
| /** |
| * @param descriptor |
| * @return the SaveablesProvider, or null |
| */ |
| private SaveablesProvider createSaveablesProvider(NavigatorContentDescriptor descriptor) { |
| NavigatorContentExtension extension = contentService |
| .getExtension(descriptor, true); |
| ITreeContentProvider contentProvider = extension |
| .getContentProvider(); |
| |
| return (SaveablesProvider)AdaptabilityUtility.getAdapter(contentProvider, SaveablesProvider.class); |
| } |
| |
| private Saveable[] getShownSaveables(Saveable[] saveables) { |
| Set result = new HashSet(Arrays.asList(currentSaveables)); |
| result.retainAll(Arrays.asList(saveables)); |
| return (Saveable[]) result.toArray(new Saveable[result.size()]); |
| } |
| |
| private void recomputeSaveablesAndNotify(boolean recomputeProviders, |
| String startedBundleIdOrNull) { |
| if (recomputeProviders && startedBundleIdOrNull == null |
| && saveablesProviders != null) { |
| // a bundle was stopped, dispose of all saveablesProviders and |
| // recompute |
| // TODO optimize this |
| for (int i = 0; i < saveablesProviders.length; i++) { |
| saveablesProviders[i].dispose(); |
| } |
| saveablesProviders = null; |
| } else if (startedBundleIdOrNull != null){ |
| if(inactivePluginsWithSaveablesProviders.containsKey(startedBundleIdOrNull)) { |
| updateSaveablesProviders(startedBundleIdOrNull); |
| } |
| } |
| Set oldSaveables = new HashSet(Arrays.asList(currentSaveables)); |
| currentSaveables = computeSaveables(); |
| Set newSaveables = new HashSet(Arrays.asList(currentSaveables)); |
| final Set removedSaveables = new HashSet(oldSaveables); |
| removedSaveables.removeAll(newSaveables); |
| final Set addedSaveables = new HashSet(newSaveables); |
| addedSaveables.removeAll(oldSaveables); |
| if (addedSaveables.size() > 0) { |
| Display.getDefault().asyncExec(new Runnable() { |
| public void run() { |
| // We might be disposed at this point. |
| // One indication of this is that saveablesSource is null. |
| if (saveablesSource == null) { |
| return; |
| } |
| outsideListener.handleLifecycleEvent(new SaveablesLifecycleEvent( |
| saveablesSource, SaveablesLifecycleEvent.POST_OPEN, |
| (Saveable[]) addedSaveables |
| .toArray(new Saveable[addedSaveables.size()]), |
| false)); |
| } |
| }); |
| } |
| // TODO this will make the closing of saveables non-cancelable. |
| // Ideally, we should react to PRE_CLOSE events and fire |
| // an appropriate PRE_CLOSE |
| if (removedSaveables.size() > 0) { |
| Display.getDefault().asyncExec(new Runnable() { |
| public void run() { |
| // we might be disposed at this point |
| // One indication of this is that saveablesSource is null. |
| if (saveablesSource == null) { |
| return; |
| } |
| outsideListener |
| .handleLifecycleEvent(new SaveablesLifecycleEvent( |
| saveablesSource, |
| SaveablesLifecycleEvent.PRE_CLOSE, |
| (Saveable[]) removedSaveables |
| .toArray(new Saveable[removedSaveables |
| .size()]), true)); |
| outsideListener |
| .handleLifecycleEvent(new SaveablesLifecycleEvent( |
| saveablesSource, |
| SaveablesLifecycleEvent.POST_CLOSE, |
| (Saveable[]) removedSaveables |
| .toArray(new Saveable[removedSaveables |
| .size()]), false)); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * @param startedBundleId |
| */ |
| private void updateSaveablesProviders(String startedBundleId) { |
| List result = new ArrayList(Arrays.asList(saveablesProviders)); |
| List descriptors = (List) inactivePluginsWithSaveablesProviders |
| .get(startedBundleId); |
| for (Iterator it = descriptors.iterator(); it.hasNext();) { |
| NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) it |
| .next(); |
| SaveablesProvider saveablesProvider = createSaveablesProvider(descriptor); |
| if (saveablesProvider != null) { |
| saveablesProvider.init(saveablesLifecycleListener); |
| result.add(saveablesProvider); |
| saveablesProviderMap.put(descriptor, saveablesProvider); |
| } |
| } |
| saveablesProviders = (SaveablesProvider[]) result |
| .toArray(new SaveablesProvider[result.size()]); |
| } |
| |
| /** |
| * @param symbolicName |
| */ |
| private synchronized void handleBundleStarted(String symbolicName) { |
| // Guard against the case that this instance is not yet initialized, |
| // or already disposed. |
| if (saveablesSource != null) { |
| if (inactivePluginsWithSaveablesProviders.containsKey(symbolicName)) { |
| recomputeSaveablesAndNotify(true, symbolicName); |
| } |
| } |
| } |
| |
| /** |
| * @param symbolicName |
| */ |
| private synchronized void handleBundleStopped(String symbolicName) { |
| // Guard against the case that this instance is not yet initialized, |
| // or already disposed. |
| if (saveablesSource != null) { |
| recomputeSaveablesAndNotify(true, null); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.internal.navigator.VisibilityAssistant.VisibilityListener#onVisibilityOrActivationChange() |
| */ |
| public synchronized void onVisibilityOrActivationChange() { |
| // Guard against the case that this instance is not yet initialized, |
| // or already disposed. |
| if (saveablesSource != null) { |
| recomputeSaveablesAndNotify(true, null); |
| } |
| } |
| |
| } |