001: /*******************************************************************************
002: * Copyright (c) 2006, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.navigator;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.HashMap;
015: import java.util.HashSet;
016: import java.util.Iterator;
017: import java.util.List;
018: import java.util.Map;
019: import java.util.Set;
020: import java.util.TreeMap;
021:
022: import org.eclipse.core.runtime.Platform;
023: import org.eclipse.jface.viewers.IStructuredSelection;
024: import org.eclipse.jface.viewers.ITreeContentProvider;
025: import org.eclipse.jface.viewers.ITreePathContentProvider;
026: import org.eclipse.jface.viewers.ITreeSelection;
027: import org.eclipse.jface.viewers.StructuredViewer;
028: import org.eclipse.jface.viewers.TreePath;
029: import org.eclipse.swt.events.DisposeEvent;
030: import org.eclipse.swt.events.DisposeListener;
031: import org.eclipse.swt.widgets.Display;
032: import org.eclipse.ui.ISaveablesLifecycleListener;
033: import org.eclipse.ui.ISaveablesSource;
034: import org.eclipse.ui.Saveable;
035: import org.eclipse.ui.SaveablesLifecycleEvent;
036: import org.eclipse.ui.internal.navigator.VisibilityAssistant.VisibilityListener;
037: import org.eclipse.ui.internal.navigator.extensions.ExtensionPriorityComparator;
038: import org.eclipse.ui.internal.navigator.extensions.NavigatorContentDescriptor;
039: import org.eclipse.ui.internal.navigator.extensions.NavigatorContentExtension;
040: import org.eclipse.ui.navigator.INavigatorContentDescriptor;
041: import org.eclipse.ui.navigator.INavigatorSaveablesService;
042: import org.eclipse.ui.navigator.SaveablesProvider;
043: import org.osgi.framework.Bundle;
044: import org.osgi.framework.BundleEvent;
045:
046: /**
047: * Implementation of INavigatorSaveablesService.
048: * <p>
049: * Implementation note: all externally callable methods are synchronized. The
050: * private helper methods are not synchronized since they can only be called
051: * from methods that already hold the lock.
052: * </p>
053: * @since 3.2
054: *
055: */
056: public class NavigatorSaveablesService implements
057: INavigatorSaveablesService, VisibilityListener {
058:
059: private NavigatorContentService contentService;
060:
061: private static List instances = new ArrayList();
062:
063: /**
064: * @param contentService
065: */
066: public NavigatorSaveablesService(
067: NavigatorContentService contentService) {
068: this .contentService = contentService;
069: }
070:
071: private static void addInstance(
072: NavigatorSaveablesService saveablesService) {
073: synchronized (instances) {
074: instances.add(saveablesService);
075: }
076: }
077:
078: private static void removeInstance(
079: NavigatorSaveablesService saveablesService) {
080: synchronized (instances) {
081: instances.remove(saveablesService);
082: }
083: }
084:
085: /**
086: * @param event
087: */
088: /* package */static void bundleChanged(BundleEvent event) {
089: synchronized (instances) {
090: if (event.getType() == BundleEvent.STARTED) {
091: // System.out.println("bundle started: " + event.getBundle().getSymbolicName()); //$NON-NLS-1$
092: for (Iterator it = instances.iterator(); it.hasNext();) {
093: NavigatorSaveablesService instance = (NavigatorSaveablesService) it
094: .next();
095: instance.handleBundleStarted(event.getBundle()
096: .getSymbolicName());
097: }
098: } else if (event.getType() == BundleEvent.STOPPED) {
099: // System.out.println("bundle stopped: " + event.getBundle().getSymbolicName()); //$NON-NLS-1$
100: for (Iterator it = instances.iterator(); it.hasNext();) {
101: NavigatorSaveablesService instance = (NavigatorSaveablesService) it
102: .next();
103: instance.handleBundleStopped(event.getBundle()
104: .getSymbolicName());
105: }
106: }
107: }
108: }
109:
110: private class LifecycleListener implements
111: ISaveablesLifecycleListener {
112: public void handleLifecycleEvent(SaveablesLifecycleEvent event) {
113: Saveable[] saveables = event.getSaveables();
114: switch (event.getEventType()) {
115: case SaveablesLifecycleEvent.POST_OPEN:
116: recomputeSaveablesAndNotify(false, null);
117: break;
118: case SaveablesLifecycleEvent.POST_CLOSE:
119: recomputeSaveablesAndNotify(false, null);
120: break;
121: case SaveablesLifecycleEvent.DIRTY_CHANGED:
122: Saveable[] shownSaveables = getShownSaveables(saveables);
123: if (shownSaveables.length > 0) {
124: outsideListener
125: .handleLifecycleEvent(new SaveablesLifecycleEvent(
126: saveablesSource,
127: SaveablesLifecycleEvent.DIRTY_CHANGED,
128: shownSaveables, false));
129: }
130: break;
131: }
132: }
133: }
134:
135: private Saveable[] currentSaveables;
136:
137: private ISaveablesLifecycleListener outsideListener;
138:
139: private ISaveablesLifecycleListener saveablesLifecycleListener = new LifecycleListener();
140:
141: private ISaveablesSource saveablesSource;
142:
143: private StructuredViewer viewer;
144:
145: private SaveablesProvider[] saveablesProviders;
146:
147: private DisposeListener disposeListener = new DisposeListener() {
148:
149: public void widgetDisposed(DisposeEvent e) {
150: // synchronize in the same order as in the init method.
151: synchronized (instances) {
152: synchronized (NavigatorSaveablesService.this ) {
153: if (saveablesProviders != null) {
154: for (int i = 0; i < saveablesProviders.length; i++) {
155: saveablesProviders[i].dispose();
156: }
157: }
158: removeInstance(NavigatorSaveablesService.this );
159: contentService = null;
160: currentSaveables = null;
161: outsideListener = null;
162: saveablesLifecycleListener = null;
163: saveablesSource = null;
164: viewer = null;
165: saveablesProviders = null;
166: disposeListener = null;
167: }
168: }
169: }
170: };
171:
172: private Map inactivePluginsWithSaveablesProviders;
173:
174: /**
175: * a TreeMap (NavigatorContentDescriptor->SaveablesProvider) which uses
176: * ExtensionPriorityComparator.INSTANCE as its Comparator
177: */
178: private Map saveablesProviderMap;
179:
180: /**
181: * Implementation note: This is not synchronized at the method level because it needs to
182: * synchronize on "instances" first, then on "this", to avoid potential deadlock.
183: *
184: * @param saveablesSource
185: * @param viewer
186: * @param outsideListener
187: *
188: */
189: public void init(final ISaveablesSource saveablesSource,
190: final StructuredViewer viewer,
191: ISaveablesLifecycleListener outsideListener) {
192: // Synchronize on instances to make sure that we don't miss bundle started events.
193: synchronized (instances) {
194: // Synchronize on this because we are calling computeSaveables.
195: // Synchronization must remain in this order to avoid deadlock.
196: // This might not be necessary because at this time, no other
197: // concurrent calls should be possible, but it doesn't hurt either.
198: // For example, the initialization sequence might change in the
199: // future.
200: synchronized (this ) {
201: this .saveablesSource = saveablesSource;
202: this .viewer = viewer;
203: this .outsideListener = outsideListener;
204: currentSaveables = computeSaveables();
205: // add this instance after we are fully inialized.
206: addInstance(this );
207: }
208: }
209: viewer.getControl().addDisposeListener(disposeListener);
210: }
211:
212: /** helper to compute the saveables for which elements are part of the tree.
213: * Must be called from a synchronized method.
214: *
215: * @return the saveables
216: */
217: private Saveable[] computeSaveables() {
218: ITreeContentProvider contentProvider = (ITreeContentProvider) viewer
219: .getContentProvider();
220: boolean isTreepathContentProvider = contentProvider instanceof ITreePathContentProvider;
221: Object viewerInput = viewer.getInput();
222: List result = new ArrayList();
223: Set roots = new HashSet(Arrays.asList(contentProvider
224: .getElements(viewerInput)));
225: SaveablesProvider[] saveablesProviders = getSaveablesProviders();
226: for (int i = 0; i < saveablesProviders.length; i++) {
227: SaveablesProvider saveablesProvider = saveablesProviders[i];
228: Saveable[] saveables = saveablesProvider.getSaveables();
229: for (int j = 0; j < saveables.length; j++) {
230: Saveable saveable = saveables[j];
231: Object[] elements = saveablesProvider
232: .getElements(saveable);
233: // the saveable is added to the result if at least one of the
234: // elements representing the saveable appears in the tree, i.e.
235: // if its parent chain leads to a root node.
236: boolean foundRoot = false;
237: for (int k = 0; !foundRoot && k < elements.length; k++) {
238: Object element = elements[k];
239: if (roots.contains(element)) {
240: result.add(saveable);
241: foundRoot = true;
242: } else if (isTreepathContentProvider) {
243: ITreePathContentProvider treePathContentProvider = (ITreePathContentProvider) contentProvider;
244: TreePath[] parentPaths = treePathContentProvider
245: .getParents(element);
246: for (int l = 0; !foundRoot
247: && l < parentPaths.length; l++) {
248: TreePath parentPath = parentPaths[l];
249: for (int m = 0; !foundRoot
250: && m < parentPath.getSegmentCount(); m++) {
251: if (roots.contains(parentPath
252: .getSegment(m))) {
253: result.add(saveable);
254: foundRoot = true;
255: }
256: }
257: }
258: } else {
259: while (!foundRoot && element != null) {
260: if (roots.contains(element)) {
261: // found a parent chain leading to a root. The
262: // saveable is part of the tree.
263: result.add(saveable);
264: foundRoot = true;
265: } else {
266: element = contentProvider
267: .getParent(element);
268: }
269: }
270: }
271: }
272: }
273: }
274: return (Saveable[]) result.toArray(new Saveable[result.size()]);
275: }
276:
277: public synchronized Saveable[] getActiveSaveables() {
278: ITreeContentProvider contentProvider = (ITreeContentProvider) viewer
279: .getContentProvider();
280: IStructuredSelection selection = (IStructuredSelection) viewer
281: .getSelection();
282: if (selection instanceof ITreeSelection) {
283: return getActiveSaveablesFromTreeSelection((ITreeSelection) selection);
284: } else if (contentProvider instanceof ITreePathContentProvider) {
285: return getActiveSaveablesFromTreePathProvider(selection,
286: (ITreePathContentProvider) contentProvider);
287: } else {
288: return getActiveSaveablesFromTreeProvider(selection,
289: contentProvider);
290: }
291: }
292:
293: /**
294: * @param selection
295: * @return the active saveables
296: */
297: private Saveable[] getActiveSaveablesFromTreeSelection(
298: ITreeSelection selection) {
299: Set result = new HashSet();
300: TreePath[] paths = selection.getPaths();
301: for (int i = 0; i < paths.length; i++) {
302: TreePath path = paths[i];
303: Saveable saveable = findSaveable(path);
304: if (saveable != null) {
305: result.add(saveable);
306: }
307: }
308: return (Saveable[]) result.toArray(new Saveable[result.size()]);
309: }
310:
311: /**
312: * @param selection
313: * @param provider
314: * @return the active saveables
315: */
316: private Saveable[] getActiveSaveablesFromTreePathProvider(
317: IStructuredSelection selection,
318: ITreePathContentProvider provider) {
319: Set result = new HashSet();
320: for (Iterator it = selection.iterator(); it.hasNext();) {
321: Object element = it.next();
322: Saveable saveable = getSaveable(element);
323: if (saveable != null) {
324: result.add(saveable);
325: } else {
326: TreePath[] paths = provider.getParents(element);
327: saveable = findSaveable(paths);
328: if (saveable != null) {
329: result.add(saveable);
330: }
331: }
332: }
333: return (Saveable[]) result.toArray(new Saveable[result.size()]);
334: }
335:
336: /**
337: * @param selection
338: * @param contentProvider
339: * @return the active saveables
340: */
341: private Saveable[] getActiveSaveablesFromTreeProvider(
342: IStructuredSelection selection,
343: ITreeContentProvider contentProvider) {
344: Set result = new HashSet();
345: for (Iterator it = selection.iterator(); it.hasNext();) {
346: Object element = it.next();
347: Saveable saveable = findSaveable(element, contentProvider);
348: if (saveable != null) {
349: result.add(saveable);
350: }
351: }
352: return (Saveable[]) result.toArray(new Saveable[result.size()]);
353: }
354:
355: /**
356: * @param element
357: * @param contentProvider
358: * @return the saveable, or null
359: */
360: private Saveable findSaveable(Object element,
361: ITreeContentProvider contentProvider) {
362: while (element != null) {
363: Saveable saveable = getSaveable(element);
364: if (saveable != null) {
365: return saveable;
366: }
367: element = contentProvider.getParent(element);
368: }
369: return null;
370: }
371:
372: /**
373: * @param paths
374: * @return the saveable, or null
375: */
376: private Saveable findSaveable(TreePath[] paths) {
377: for (int i = 0; i < paths.length; i++) {
378: Saveable saveable = findSaveable(paths[i]);
379: if (saveable != null) {
380: return saveable;
381: }
382: }
383: return null;
384: }
385:
386: /**
387: * @param path
388: * @return a saveable, or null
389: */
390: private Saveable findSaveable(TreePath path) {
391: int count = path.getSegmentCount();
392: for (int j = count - 1; j >= 0; j--) {
393: Object parent = path.getSegment(j);
394: Saveable saveable = getSaveable(parent);
395: if (saveable != null) {
396: return saveable;
397: }
398: }
399: return null;
400: }
401:
402: /**
403: * @param element
404: * @return the saveable associated with the given element
405: */
406: private Saveable getSaveable(Object element) {
407: if (saveablesProviderMap == null) {
408: // has the side effect of recomputing saveablesProviderMap:
409: getSaveablesProviders();
410: }
411: for (Iterator sItr = saveablesProviderMap.keySet().iterator(); sItr
412: .hasNext();) {
413: NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) sItr
414: .next();
415: if (descriptor.isTriggerPoint(element)
416: || descriptor.isPossibleChild(element)) {
417: SaveablesProvider provider = (SaveablesProvider) saveablesProviderMap
418: .get(descriptor);
419: Saveable saveable = provider.getSaveable(element);
420: if (saveable != null) {
421: return saveable;
422: }
423: }
424: }
425: return null;
426: }
427:
428: /**
429: * @return the saveables
430: */
431: public synchronized Saveable[] getSaveables() {
432: return currentSaveables;
433: }
434:
435: /**
436: * @return all SaveablesProvider objects
437: */
438: private SaveablesProvider[] getSaveablesProviders() {
439: // TODO optimize this
440: if (saveablesProviders == null) {
441: inactivePluginsWithSaveablesProviders = new HashMap();
442: saveablesProviderMap = new TreeMap(
443: ExtensionPriorityComparator.INSTANCE);
444: INavigatorContentDescriptor[] descriptors = contentService
445: .getActiveDescriptorsWithSaveables();
446: List result = new ArrayList();
447: for (int i = 0; i < descriptors.length; i++) {
448: NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) descriptors[i];
449: String pluginId = ((NavigatorContentDescriptor) descriptor)
450: .getContribution().getPluginId();
451: if (Platform.getBundle(pluginId).getState() != Bundle.ACTIVE) {
452: List inactiveDescriptors = (List) inactivePluginsWithSaveablesProviders
453: .get(pluginId);
454: if (inactiveDescriptors == null) {
455: inactiveDescriptors = new ArrayList();
456: inactivePluginsWithSaveablesProviders.put(
457: pluginId, inactiveDescriptors);
458: }
459: inactiveDescriptors.add(descriptor);
460: } else {
461: SaveablesProvider saveablesProvider = createSaveablesProvider(descriptor);
462: if (saveablesProvider != null) {
463: saveablesProvider
464: .init(saveablesLifecycleListener);
465: result.add(saveablesProvider);
466: saveablesProviderMap.put(descriptor,
467: saveablesProvider);
468: }
469: }
470: }
471: saveablesProviders = (SaveablesProvider[]) result
472: .toArray(new SaveablesProvider[result.size()]);
473: }
474: return saveablesProviders;
475: }
476:
477: /**
478: * @param descriptor
479: * @return the SaveablesProvider, or null
480: */
481: private SaveablesProvider createSaveablesProvider(
482: NavigatorContentDescriptor descriptor) {
483: NavigatorContentExtension extension = contentService
484: .getExtension(descriptor, true);
485: ITreeContentProvider contentProvider = extension
486: .getContentProvider();
487:
488: return (SaveablesProvider) AdaptabilityUtility.getAdapter(
489: contentProvider, SaveablesProvider.class);
490: }
491:
492: private Saveable[] getShownSaveables(Saveable[] saveables) {
493: Set result = new HashSet(Arrays.asList(currentSaveables));
494: result.retainAll(Arrays.asList(saveables));
495: return (Saveable[]) result.toArray(new Saveable[result.size()]);
496: }
497:
498: private void recomputeSaveablesAndNotify(
499: boolean recomputeProviders, String startedBundleIdOrNull) {
500: if (recomputeProviders && startedBundleIdOrNull == null
501: && saveablesProviders != null) {
502: // a bundle was stopped, dispose of all saveablesProviders and
503: // recompute
504: // TODO optimize this
505: for (int i = 0; i < saveablesProviders.length; i++) {
506: saveablesProviders[i].dispose();
507: }
508: saveablesProviders = null;
509: } else if (startedBundleIdOrNull != null) {
510: if (inactivePluginsWithSaveablesProviders
511: .containsKey(startedBundleIdOrNull)) {
512: updateSaveablesProviders(startedBundleIdOrNull);
513: }
514: }
515: Set oldSaveables = new HashSet(Arrays.asList(currentSaveables));
516: currentSaveables = computeSaveables();
517: Set newSaveables = new HashSet(Arrays.asList(currentSaveables));
518: final Set removedSaveables = new HashSet(oldSaveables);
519: removedSaveables.removeAll(newSaveables);
520: final Set addedSaveables = new HashSet(newSaveables);
521: addedSaveables.removeAll(oldSaveables);
522: if (addedSaveables.size() > 0) {
523: Display.getDefault().asyncExec(new Runnable() {
524: public void run() {
525: // We might be disposed at this point.
526: // One indication of this is that saveablesSource is null.
527: if (saveablesSource == null) {
528: return;
529: }
530: outsideListener
531: .handleLifecycleEvent(new SaveablesLifecycleEvent(
532: saveablesSource,
533: SaveablesLifecycleEvent.POST_OPEN,
534: (Saveable[]) addedSaveables
535: .toArray(new Saveable[addedSaveables
536: .size()]), false));
537: }
538: });
539: }
540: // TODO this will make the closing of saveables non-cancelable.
541: // Ideally, we should react to PRE_CLOSE events and fire
542: // an appropriate PRE_CLOSE
543: if (removedSaveables.size() > 0) {
544: Display.getDefault().asyncExec(new Runnable() {
545: public void run() {
546: // we might be disposed at this point
547: // One indication of this is that saveablesSource is null.
548: if (saveablesSource == null) {
549: return;
550: }
551: outsideListener
552: .handleLifecycleEvent(new SaveablesLifecycleEvent(
553: saveablesSource,
554: SaveablesLifecycleEvent.PRE_CLOSE,
555: (Saveable[]) removedSaveables
556: .toArray(new Saveable[removedSaveables
557: .size()]), true));
558: outsideListener
559: .handleLifecycleEvent(new SaveablesLifecycleEvent(
560: saveablesSource,
561: SaveablesLifecycleEvent.POST_CLOSE,
562: (Saveable[]) removedSaveables
563: .toArray(new Saveable[removedSaveables
564: .size()]), false));
565: }
566: });
567: }
568: }
569:
570: /**
571: * @param startedBundleId
572: */
573: private void updateSaveablesProviders(String startedBundleId) {
574: List result = new ArrayList(Arrays.asList(saveablesProviders));
575: List descriptors = (List) inactivePluginsWithSaveablesProviders
576: .get(startedBundleId);
577: for (Iterator it = descriptors.iterator(); it.hasNext();) {
578: NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) it
579: .next();
580: SaveablesProvider saveablesProvider = createSaveablesProvider(descriptor);
581: if (saveablesProvider != null) {
582: saveablesProvider.init(saveablesLifecycleListener);
583: result.add(saveablesProvider);
584: saveablesProviderMap.put(descriptor, saveablesProvider);
585: }
586: }
587: saveablesProviders = (SaveablesProvider[]) result
588: .toArray(new SaveablesProvider[result.size()]);
589: }
590:
591: /**
592: * @param symbolicName
593: */
594: private synchronized void handleBundleStarted(String symbolicName) {
595: // Guard against the case that this instance is not yet initialized,
596: // or already disposed.
597: if (saveablesSource != null) {
598: if (inactivePluginsWithSaveablesProviders
599: .containsKey(symbolicName)) {
600: recomputeSaveablesAndNotify(true, symbolicName);
601: }
602: }
603: }
604:
605: /**
606: * @param symbolicName
607: */
608: private synchronized void handleBundleStopped(String symbolicName) {
609: // Guard against the case that this instance is not yet initialized,
610: // or already disposed.
611: if (saveablesSource != null) {
612: recomputeSaveablesAndNotify(true, null);
613: }
614: }
615:
616: /* (non-Javadoc)
617: * @see org.eclipse.ui.internal.navigator.VisibilityAssistant.VisibilityListener#onVisibilityOrActivationChange()
618: */
619: public synchronized void onVisibilityOrActivationChange() {
620: // Guard against the case that this instance is not yet initialized,
621: // or already disposed.
622: if (saveablesSource != null) {
623: recomputeSaveablesAndNotify(true, null);
624: }
625: }
626:
627: }
|