Project

General

Profile

Download (19.9 KB) Statistics
| Branch: | Tag: | Revision:
1
/*******************************************************************************
2
 * Copyright (c) 2006, 2010 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     IBM Corporation - initial API and implementation
10
 *******************************************************************************/
11

    
12
package org.eclipse.ui.internal.navigator;
13

    
14
import java.util.ArrayList;
15
import java.util.Arrays;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.Iterator;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.TreeMap;
23

    
24
import org.eclipse.core.runtime.Platform;
25
import org.eclipse.jface.viewers.IStructuredSelection;
26
import org.eclipse.jface.viewers.ITreeContentProvider;
27
import org.eclipse.jface.viewers.ITreePathContentProvider;
28
import org.eclipse.jface.viewers.ITreeSelection;
29
import org.eclipse.jface.viewers.StructuredViewer;
30
import org.eclipse.jface.viewers.TreePath;
31
import org.eclipse.swt.events.DisposeEvent;
32
import org.eclipse.swt.events.DisposeListener;
33
import org.eclipse.swt.widgets.Display;
34
import org.eclipse.ui.ISaveablesLifecycleListener;
35
import org.eclipse.ui.ISaveablesSource;
36
import org.eclipse.ui.Saveable;
37
import org.eclipse.ui.SaveablesLifecycleEvent;
38
import org.eclipse.ui.internal.navigator.VisibilityAssistant.VisibilityListener;
39
import org.eclipse.ui.internal.navigator.extensions.ExtensionSequenceNumberComparator;
40
import org.eclipse.ui.internal.navigator.extensions.NavigatorContentDescriptor;
41
import org.eclipse.ui.internal.navigator.extensions.NavigatorContentExtension;
42
import org.eclipse.ui.navigator.INavigatorContentDescriptor;
43
import org.eclipse.ui.navigator.INavigatorSaveablesService;
44
import org.eclipse.ui.navigator.SaveablesProvider;
45
import org.osgi.framework.Bundle;
46
import org.osgi.framework.BundleEvent;
47

    
48
/**
49
 * Implementation of INavigatorSaveablesService.
50
 * <p>
51
 * Implementation note: all externally callable methods are synchronized. The
52
 * private helper methods are not synchronized since they can only be called
53
 * from methods that already hold the lock.
54
 * </p>
55
 * @since 3.2
56
 * 
57
 */
58
public class NavigatorSaveablesService implements INavigatorSaveablesService, VisibilityListener {
59

    
60
	private NavigatorContentService contentService;
61

    
62
	private static List instances = new ArrayList();
63

    
64
	/**
65
	 * @param contentService
66
	 */
67
	public NavigatorSaveablesService(NavigatorContentService contentService) {
68
		this.contentService = contentService;
69
	}
70

    
71
	private static void addInstance(NavigatorSaveablesService saveablesService) {
72
		synchronized (instances) {
73
			instances.add(saveablesService);
74
		}
75
	}
76

    
77
	private static void removeInstance(
78
			NavigatorSaveablesService saveablesService) {
79
		synchronized (instances) {
80
			instances.remove(saveablesService);
81
		}
82
	}
83

    
84
	/**
85
	 * @param event
86
	 */
87
	/* package */ static void bundleChanged(BundleEvent event) {
88
		synchronized(instances) {
89
			if (event.getType() == BundleEvent.STARTED) {
90
				// System.out.println("bundle started: " + event.getBundle().getSymbolicName()); //$NON-NLS-1$
91
				for (Iterator it = instances.iterator(); it.hasNext();) {
92
					NavigatorSaveablesService instance = (NavigatorSaveablesService) it
93
							.next();
94
					instance.handleBundleStarted(event.getBundle()
95
							.getSymbolicName());
96
				}
97
			} else if (event.getType() == BundleEvent.STOPPED) {
98
				// System.out.println("bundle stopped: " + event.getBundle().getSymbolicName()); //$NON-NLS-1$
99
				for (Iterator it = instances.iterator(); it.hasNext();) {
100
					NavigatorSaveablesService instance = (NavigatorSaveablesService) it
101
							.next();
102
					instance.handleBundleStopped(event.getBundle()
103
							.getSymbolicName());
104
				}
105
			}
106
		}
107
	}
108

    
109
	private class LifecycleListener implements ISaveablesLifecycleListener {
110
		public void handleLifecycleEvent(SaveablesLifecycleEvent event) {
111
			Saveable[] saveables = event.getSaveables();
112
			Saveable[] shownSaveables = null;
113
			// synchronize in the same order as in the init method.
114
			synchronized (instances) {
115
				synchronized (NavigatorSaveablesService.this) {
116
					if (isDisposed())
117
						return;
118
					switch (event.getEventType()) {
119
					case SaveablesLifecycleEvent.POST_OPEN:
120
						recomputeSaveablesAndNotify(false, null);
121
						break;
122
					case SaveablesLifecycleEvent.POST_CLOSE:
123
						recomputeSaveablesAndNotify(false, null);
124
						break;
125
					case SaveablesLifecycleEvent.DIRTY_CHANGED:
126
						Set result = new HashSet(Arrays.asList(currentSaveables));
127
						result.retainAll(Arrays.asList(saveables));
128
						shownSaveables = (Saveable[]) result.toArray(new Saveable[result.size()]);
129
						break;
130
					}
131
				}
132
			}
133

    
134
			// Notify outside of synchronization
135
			if (shownSaveables != null && shownSaveables.length > 0) {
136
				outsideListener.handleLifecycleEvent(new SaveablesLifecycleEvent(saveablesSource, SaveablesLifecycleEvent.DIRTY_CHANGED,
137
						shownSaveables, false));
138
			}
139
		}
140
	}
141

    
142
	private Saveable[] currentSaveables;
143

    
144
	private ISaveablesLifecycleListener outsideListener;
145

    
146
	private ISaveablesLifecycleListener saveablesLifecycleListener = new LifecycleListener();
147

    
148
	private ISaveablesSource saveablesSource;
149

    
150
	private StructuredViewer viewer;
151

    
152
	private SaveablesProvider[] saveablesProviders;
153

    
154
	private DisposeListener disposeListener = new DisposeListener() {
155

    
156
		public void widgetDisposed(DisposeEvent e) {
157
			// synchronize in the same order as in the init method.
158
			synchronized (instances) {
159
				synchronized (NavigatorSaveablesService.this) {
160
					if (saveablesProviders != null) {
161
						for (int i = 0; i < saveablesProviders.length; i++) {
162
							saveablesProviders[i].dispose();
163
						}
164
					}
165
					removeInstance(NavigatorSaveablesService.this);
166
					contentService = null;
167
					currentSaveables = null;
168
					outsideListener = null;
169
					saveablesLifecycleListener = null;
170
					saveablesSource = null;
171
					viewer = null;
172
					saveablesProviders = null;
173
					disposeListener = null;
174
				}
175
			}
176
		}
177
	};
178

    
179
	private Map inactivePluginsWithSaveablesProviders;
180

    
181
    /**
182
	 * a TreeMap (NavigatorContentDescriptor->SaveablesProvider) which uses
183
	 * ExtensionPriorityComparator.INSTANCE as its Comparator
184
	 */
185
	private Map saveablesProviderMap;
186

    
187
	/**
188
	 * Implementation note: This is not synchronized at the method level because it needs to
189
	 * synchronize on "instances" first, then on "this", to avoid potential deadlock.
190
	 * 
191
	 * @param saveablesSource
192
	 * @param viewer
193
	 * @param outsideListener
194
	 * 
195
	 */
196
	public void init(final ISaveablesSource saveablesSource,
197
			final StructuredViewer viewer,
198
			ISaveablesLifecycleListener outsideListener) {
199
		// Synchronize on instances to make sure that we don't miss bundle started events. 
200
		synchronized (instances) {
201
			// Synchronize on this because we are calling computeSaveables.
202
			// Synchronization must remain in this order to avoid deadlock.
203
			// This might not be necessary because at this time, no other
204
			// concurrent calls should be possible, but it doesn't hurt either.
205
			// For example, the initialization sequence might change in the
206
			// future.
207
			synchronized (this) {
208
				this.saveablesSource = saveablesSource;
209
				this.viewer = viewer;
210
				this.outsideListener = outsideListener;
211
				currentSaveables = computeSaveables();
212
				// add this instance after we are fully inialized.
213
				addInstance(this);
214
			}
215
		}
216
		viewer.getControl().addDisposeListener(disposeListener);
217
	}
218

    
219
	private boolean isDisposed() {
220
		return contentService == null;
221
	}
222
	
223
	/** helper to compute the saveables for which elements are part of the tree.
224
	 * Must be called from a synchronized method.
225
	 * 
226
	 * @return the saveables
227
	 */ 
228
	private Saveable[] computeSaveables() {
229
		ITreeContentProvider contentProvider = (ITreeContentProvider) viewer
230
				.getContentProvider();
231
		boolean isTreepathContentProvider = contentProvider instanceof ITreePathContentProvider;
232
		Object viewerInput = viewer.getInput();
233
		List result = new ArrayList();
234
		Set roots = new HashSet(Arrays.asList(contentProvider
235
				.getElements(viewerInput)));
236
		SaveablesProvider[] saveablesProviders = getSaveablesProviders();
237
		for (int i = 0; i < saveablesProviders.length; i++) {
238
			SaveablesProvider saveablesProvider = saveablesProviders[i];
239
			Saveable[] saveables = saveablesProvider.getSaveables();
240
			for (int j = 0; j < saveables.length; j++) {
241
				Saveable saveable = saveables[j];
242
				Object[] elements = saveablesProvider.getElements(saveable);
243
				// the saveable is added to the result if at least one of the
244
				// elements representing the saveable appears in the tree, i.e.
245
				// if its parent chain leads to a root node.
246
				boolean foundRoot = false;
247
				for (int k = 0; !foundRoot && k < elements.length; k++) {
248
					Object element = elements[k];
249
					if (roots.contains(element)) {
250
					    result.add(saveable);
251
					    foundRoot = true;
252
					} else if (isTreepathContentProvider) {
253
						ITreePathContentProvider treePathContentProvider = (ITreePathContentProvider) contentProvider;
254
						TreePath[] parentPaths = treePathContentProvider.getParents(element);
255
						for (int l = 0; !foundRoot && l < parentPaths.length; l++) {
256
							TreePath parentPath = parentPaths[l];
257
                            for (int m = 0; !foundRoot && m < parentPath.getSegmentCount(); m++) {
258
                                if (roots.contains(parentPath.getSegment(m))) {
259
                                    result.add(saveable);
260
                                    foundRoot = true;
261
                                }
262
                            }
263
						}
264
					} else {
265
						while (!foundRoot && element != null) {
266
							if (roots.contains(element)) {
267
								// found a parent chain leading to a root. The
268
								// saveable is part of the tree.
269
								result.add(saveable);
270
								foundRoot = true;
271
							} else {
272
								element = contentProvider.getParent(element);
273
							}
274
						}
275
					}
276
				}
277
			}
278
		}
279
		return (Saveable[]) result.toArray(new Saveable[result.size()]);
280
	}
281

    
282
	public synchronized Saveable[] getActiveSaveables() {
283
		ITreeContentProvider contentProvider = (ITreeContentProvider) viewer
284
				.getContentProvider();
285
		IStructuredSelection selection = (IStructuredSelection) viewer
286
				.getSelection();
287
		if (selection instanceof ITreeSelection) {
288
			return getActiveSaveablesFromTreeSelection((ITreeSelection) selection);
289
		} else if (contentProvider instanceof ITreePathContentProvider) {
290
			return getActiveSaveablesFromTreePathProvider(selection, (ITreePathContentProvider) contentProvider);
291
		} else {
292
			return getActiveSaveablesFromTreeProvider(selection, contentProvider);
293
		}
294
	}
295
	
296
	/**
297
	 * @param selection
298
	 * @return the active saveables
299
	 */
300
	private Saveable[] getActiveSaveablesFromTreeSelection(
301
			ITreeSelection selection) {
302
		Set result = new HashSet();
303
		TreePath[] paths = selection.getPaths();
304
		for (int i = 0; i < paths.length; i++) {
305
			TreePath path = paths[i];
306
			Saveable saveable = findSaveable(path);
307
			if (saveable != null) {
308
				result.add(saveable);
309
			}
310
		}
311
		return (Saveable[]) result.toArray(new Saveable[result.size()]);
312
	}
313

    
314
	/**
315
	 * @param selection
316
	 * @param provider
317
	 * @return the active saveables
318
	 */
319
	private Saveable[] getActiveSaveablesFromTreePathProvider(
320
			IStructuredSelection selection, ITreePathContentProvider provider) {
321
		Set result = new HashSet();
322
		for (Iterator it = selection.iterator(); it.hasNext();) {
323
			Object element = it.next();
324
			Saveable saveable = getSaveable(element);
325
			if (saveable != null) {
326
				result.add(saveable);
327
			} else {
328
				TreePath[] paths = provider.getParents(element);
329
				saveable = findSaveable(paths);
330
				if (saveable != null) {
331
					result.add(saveable);
332
				}
333
			}
334
		}
335
		return (Saveable[]) result.toArray(new Saveable[result.size()]);
336
	}
337

    
338
	/**
339
	 * @param selection
340
	 * @param contentProvider
341
	 * @return the active saveables
342
	 */
343
	private Saveable[] getActiveSaveablesFromTreeProvider(
344
			IStructuredSelection selection, ITreeContentProvider contentProvider) {
345
		Set result = new HashSet();
346
		for (Iterator it = selection.iterator(); it.hasNext();) {
347
			Object element = it.next();
348
			Saveable saveable = findSaveable(element, contentProvider);
349
			if (saveable != null) {
350
				result.add(saveable);
351
			}
352
		}
353
		return (Saveable[]) result.toArray(new Saveable[result.size()]);
354
	}
355

    
356
	/**
357
	 * @param element
358
	 * @param contentProvider
359
	 * @return the saveable, or null
360
	 */
361
	private Saveable findSaveable(Object element,
362
			ITreeContentProvider contentProvider) {
363
		while (element != null) {
364
			Saveable saveable = getSaveable(element);
365
			if (saveable != null) {
366
				return saveable;
367
			}
368
			element = contentProvider.getParent(element);
369
		}
370
		return null;
371
	}
372

    
373
	/**
374
	 * @param paths
375
	 * @return the saveable, or null
376
	 */
377
	private Saveable findSaveable(TreePath[] paths) {
378
		for (int i = 0; i < paths.length; i++) {
379
			Saveable saveable = findSaveable(paths[i]);
380
			if (saveable != null) {
381
				return saveable;
382
			}
383
		}
384
		return null;
385
	}
386
	
387
	/**
388
	 * @param path
389
	 * @return a saveable, or null
390
	 */
391
	private Saveable findSaveable(TreePath path) {
392
		int count = path.getSegmentCount();
393
		for (int j = count - 1; j >= 0; j--) {
394
			Object parent = path.getSegment(j);
395
			Saveable saveable = getSaveable(parent);
396
			if (saveable != null) {
397
				return saveable;
398
			}
399
		}
400
		return null;
401
	}
402

    
403
	/**
404
	 * @param element
405
	 * @return the saveable associated with the given element
406
	 */
407
	private Saveable getSaveable(Object element) {
408
		if (saveablesProviderMap==null) {
409
			// has the side effect of recomputing saveablesProviderMap:
410
			getSaveablesProviders();
411
		}
412
        for(Iterator sItr = saveablesProviderMap.keySet().iterator(); sItr.hasNext();) {
413
        	NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) sItr.next();
414
                if(descriptor.isTriggerPoint(element) || descriptor.isPossibleChild(element)) {
415
                	SaveablesProvider provider = (SaveablesProvider) saveablesProviderMap.get(descriptor);
416
                	Saveable  saveable = provider.getSaveable(element);
417
                        if(saveable != null) {
418
                                return saveable;
419
                        }
420
                }
421
        }
422
        return null;
423
	}
424

    
425
	/**
426
	 * @return the saveables
427
	 */
428
	public synchronized Saveable[] getSaveables() {
429
		return currentSaveables;
430
	}
431

    
432
	/**
433
	 * @return all SaveablesProvider objects
434
	 */
435
	private SaveablesProvider[] getSaveablesProviders() {
436
		// TODO optimize this
437
		if (saveablesProviders == null) {
438
			inactivePluginsWithSaveablesProviders = new HashMap();
439
			saveablesProviderMap = new TreeMap(ExtensionSequenceNumberComparator.INSTANCE);
440
			INavigatorContentDescriptor[] descriptors = contentService
441
					.getActiveDescriptorsWithSaveables();
442
			List result = new ArrayList();
443
			for (int i = 0; i < descriptors.length; i++) {
444
				NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) descriptors[i];
445
				String pluginId = ((NavigatorContentDescriptor) descriptor)
446
						.getContribution().getPluginId();
447
				if (Platform.getBundle(pluginId).getState() != Bundle.ACTIVE) {
448
					List inactiveDescriptors = (List) inactivePluginsWithSaveablesProviders
449
							.get(pluginId);
450
					if (inactiveDescriptors == null) {
451
						inactiveDescriptors = new ArrayList();
452
						inactivePluginsWithSaveablesProviders.put(pluginId,
453
								inactiveDescriptors);
454
					}
455
					inactiveDescriptors.add(descriptor);
456
				} else {
457
					SaveablesProvider saveablesProvider = createSaveablesProvider(descriptor);
458
					if (saveablesProvider != null) {
459
						saveablesProvider.init(saveablesLifecycleListener);
460
						result.add(saveablesProvider);
461
						saveablesProviderMap.put(descriptor, saveablesProvider);
462
					}
463
				}
464
			}
465
			saveablesProviders = (SaveablesProvider[]) result
466
					.toArray(new SaveablesProvider[result.size()]);
467
		}
468
		return saveablesProviders;
469
	}
470

    
471
	/**
472
	 * @param descriptor
473
	 * @return the SaveablesProvider, or null
474
	 */
475
	private SaveablesProvider createSaveablesProvider(NavigatorContentDescriptor descriptor) {
476
		NavigatorContentExtension extension = contentService
477
				.getExtension(descriptor, true);
478
		// Use getContentProvider to get the client objects, this is important
479
		// for the adaptation below. See bug 306545
480
		ITreeContentProvider contentProvider = extension
481
				.getContentProvider();
482
        
483
        return (SaveablesProvider)AdaptabilityUtility.getAdapter(contentProvider, SaveablesProvider.class);
484
	}
485

    
486
	private void recomputeSaveablesAndNotify(boolean recomputeProviders,
487
			String startedBundleIdOrNull) {
488
		if (recomputeProviders && startedBundleIdOrNull == null
489
				&& saveablesProviders != null) {
490
			// a bundle was stopped, dispose of all saveablesProviders and
491
			// recompute
492
			// TODO optimize this
493
			for (int i = 0; i < saveablesProviders.length; i++) {
494
				saveablesProviders[i].dispose();
495
			}
496
			saveablesProviders = null;
497
		} else if (startedBundleIdOrNull != null){
498
			if(inactivePluginsWithSaveablesProviders.containsKey(startedBundleIdOrNull)) {
499
				updateSaveablesProviders(startedBundleIdOrNull);
500
			}
501
		}
502
		Set oldSaveables = new HashSet(Arrays.asList(currentSaveables));
503
		currentSaveables = computeSaveables();
504
		Set newSaveables = new HashSet(Arrays.asList(currentSaveables));
505
		final Set removedSaveables = new HashSet(oldSaveables);
506
		removedSaveables.removeAll(newSaveables);
507
		final Set addedSaveables = new HashSet(newSaveables);
508
		addedSaveables.removeAll(oldSaveables);
509
		if (addedSaveables.size() > 0) {
510
			Display.getDefault().asyncExec(new Runnable() {
511
				public void run() {
512
					if (isDisposed()) {
513
						return;
514
					}
515
					outsideListener.handleLifecycleEvent(new SaveablesLifecycleEvent(
516
							saveablesSource, SaveablesLifecycleEvent.POST_OPEN,
517
							(Saveable[]) addedSaveables
518
							.toArray(new Saveable[addedSaveables.size()]),
519
							false));
520
				}
521
			});
522
		}
523
		// TODO this will make the closing of saveables non-cancelable.
524
		// Ideally, we should react to PRE_CLOSE events and fire
525
		// an appropriate PRE_CLOSE
526
		if (removedSaveables.size() > 0) {
527
			Display.getDefault().asyncExec(new Runnable() {
528
				public void run() {
529
					if (isDisposed()) {
530
						return;
531
					}
532
					outsideListener
533
							.handleLifecycleEvent(new SaveablesLifecycleEvent(
534
									saveablesSource,
535
									SaveablesLifecycleEvent.PRE_CLOSE,
536
									(Saveable[]) removedSaveables
537
											.toArray(new Saveable[removedSaveables
538
													.size()]), true));
539
					outsideListener
540
							.handleLifecycleEvent(new SaveablesLifecycleEvent(
541
									saveablesSource,
542
									SaveablesLifecycleEvent.POST_CLOSE,
543
									(Saveable[]) removedSaveables
544
											.toArray(new Saveable[removedSaveables
545
													.size()]), false));
546
				}
547
			});
548
		}
549
	}
550

    
551
	/**
552
	 * @param startedBundleId
553
	 */
554
	private void updateSaveablesProviders(String startedBundleId) {
555
		List result = new ArrayList(Arrays.asList(saveablesProviders));
556
		List descriptors = (List) inactivePluginsWithSaveablesProviders
557
				.get(startedBundleId);
558
		for (Iterator it = descriptors.iterator(); it.hasNext();) {
559
			NavigatorContentDescriptor descriptor = (NavigatorContentDescriptor) it
560
					.next();
561
			SaveablesProvider saveablesProvider = createSaveablesProvider(descriptor);
562
			if (saveablesProvider != null) {
563
				saveablesProvider.init(saveablesLifecycleListener);
564
				result.add(saveablesProvider);
565
				saveablesProviderMap.put(descriptor, saveablesProvider);
566
			}
567
		}
568
		saveablesProviders = (SaveablesProvider[]) result
569
				.toArray(new SaveablesProvider[result.size()]);
570
	}
571

    
572
	/**
573
	 * @param symbolicName
574
	 */
575
	private synchronized void handleBundleStarted(String symbolicName) {
576
		if (!isDisposed()) {
577
			if (inactivePluginsWithSaveablesProviders.containsKey(symbolicName)) {
578
				recomputeSaveablesAndNotify(true, symbolicName);
579
			}
580
		}
581
	}
582

    
583
	/**
584
	 * @param symbolicName
585
	 */
586
	private synchronized void handleBundleStopped(String symbolicName) {
587
		if (!isDisposed()) {
588
			recomputeSaveablesAndNotify(true, null);
589
		}
590
	}
591

    
592
	/* (non-Javadoc)
593
	 * @see org.eclipse.ui.internal.navigator.VisibilityAssistant.VisibilityListener#onVisibilityOrActivationChange()
594
	 */
595
	public synchronized void onVisibilityOrActivationChange() {
596
		if (!isDisposed()) {
597
			recomputeSaveablesAndNotify(true, null);
598
		}
599
	}
600

    
601
}
(24-24/31)