1 /*******************************************************************************
2  * Copyright (c) 2000, 2017 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.team.ui.synchronize;
15 
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.List;
20 
21 import org.eclipse.compare.structuremergeviewer.ICompareInput;
22 import org.eclipse.core.resources.mapping.IModelProviderDescriptor;
23 import org.eclipse.core.resources.mapping.ModelProvider;
24 import org.eclipse.core.resources.mapping.ResourceMapping;
25 import org.eclipse.core.resources.mapping.ResourceMappingContext;
26 import org.eclipse.core.runtime.Adapters;
27 import org.eclipse.core.runtime.CoreException;
28 import org.eclipse.core.runtime.IProgressMonitor;
29 import org.eclipse.core.runtime.NullProgressMonitor;
30 import org.eclipse.core.runtime.jobs.Job;
31 import org.eclipse.jface.preference.PreferencePage;
32 import org.eclipse.jface.preference.PreferenceStore;
33 import org.eclipse.osgi.util.NLS;
34 import org.eclipse.swt.widgets.Shell;
35 import org.eclipse.team.core.mapping.IMergeContext;
36 import org.eclipse.team.core.mapping.ISynchronizationContext;
37 import org.eclipse.team.core.mapping.ISynchronizationScope;
38 import org.eclipse.team.core.mapping.ISynchronizationScopeManager;
39 import org.eclipse.team.core.mapping.provider.MergeContext;
40 import org.eclipse.team.core.mapping.provider.SynchronizationContext;
41 import org.eclipse.team.core.mapping.provider.SynchronizationScopeManager;
42 import org.eclipse.team.internal.core.subscribers.SubscriberDiffTreeEventHandler;
43 import org.eclipse.team.internal.ui.IPreferenceIds;
44 import org.eclipse.team.internal.ui.Policy;
45 import org.eclipse.team.internal.ui.TeamUIMessages;
46 import org.eclipse.team.internal.ui.TeamUIPlugin;
47 import org.eclipse.team.internal.ui.Utils;
48 import org.eclipse.team.internal.ui.mapping.ModelEnablementPreferencePage;
49 import org.eclipse.team.internal.ui.mapping.ModelSynchronizePage;
50 import org.eclipse.team.internal.ui.preferences.SyncViewerPreferencePage;
51 import org.eclipse.team.internal.ui.synchronize.IRefreshSubscriberListener;
52 import org.eclipse.team.internal.ui.synchronize.IRefreshable;
53 import org.eclipse.team.internal.ui.synchronize.RefreshModelParticipantJob;
54 import org.eclipse.team.internal.ui.synchronize.RefreshParticipantJob;
55 import org.eclipse.team.internal.ui.synchronize.RefreshUserNotificationPolicy;
56 import org.eclipse.team.internal.ui.synchronize.StartupPreferencePage;
57 import org.eclipse.team.internal.ui.synchronize.SubscriberRefreshSchedule;
58 import org.eclipse.team.ui.TeamUI;
59 import org.eclipse.team.ui.mapping.ISynchronizationCompareAdapter;
60 import org.eclipse.team.ui.mapping.ISynchronizationCompareInput;
61 import org.eclipse.team.ui.mapping.ITeamContentProviderDescriptor;
62 import org.eclipse.team.ui.mapping.ITeamContentProviderManager;
63 import org.eclipse.team.ui.mapping.SaveableComparison;
64 import org.eclipse.ui.IMemento;
65 import org.eclipse.ui.IPropertyListener;
66 import org.eclipse.ui.IWorkbenchPart;
67 import org.eclipse.ui.IWorkbenchSite;
68 import org.eclipse.ui.PartInitException;
69 import org.eclipse.ui.part.IPageBookViewPage;
70 import org.eclipse.ui.progress.IProgressConstants2;
71 
72 /**
73  * Synchronize participant that obtains it's synchronization state from
74  * a {@link ISynchronizationContext}.
75  * <p>
76  * This class may be subclassed by clients.
77  *
78  * @since 3.2
79  */
80 public class ModelSynchronizeParticipant extends
81 		AbstractSynchronizeParticipant {
82 
83 	/**
84 	 * Property constant used to store and retrieve the id of the active
85 	 * {@link ModelProvider} from an {@link ISynchronizePageConfiguration}. The
86 	 * active model provider will be the only one visible in the page. If
87 	 * <code>null</code> or <code>ALL_MODEL_PROVIDERS_ACTIVE</code> is
88 	 * returned, all model providers are considered active and are visible.
89 	 */
90 	public static final String P_VISIBLE_MODEL_PROVIDER = TeamUIPlugin.ID + ".activeModelProvider"; //$NON-NLS-1$
91 
92 	/**
93 	 * Constant used with the <code>P_ACTIVE_MODEL_PROVIDER</code> property to indicate
94 	 * that all enabled model providers are active.
95 	 */
96 	public static final String ALL_MODEL_PROVIDERS_VISIBLE = TeamUIPlugin.ID + ".activeModelProvider"; //$NON-NLS-1$
97 
98 	/**
99 	 * Property constant used during property change notification to indicate
100 	 * that the enabled model providers for this participant have changed.
101 	 */
102 	public static final String PROP_ENABLED_MODEL_PROVIDERS = TeamUIPlugin.ID + ".ENABLED_MODEL_PROVIDERS"; //$NON-NLS-1$
103 
104 	/**
105 	 * Property constant used during property change notification to indicate
106 	 * that the active model of this participant has changed.
107 	 */
108 	public static final String PROP_ACTIVE_SAVEABLE = TeamUIPlugin.ID + ".ACTIVE_SAVEABLE"; //$NON-NLS-1$
109 
110 	/**
111 	 * Property constant used during property change notification to indicate
112 	 * that the dirty state for the active saveable model of this participant has changed.
113 	 */
114 	public static final String PROP_DIRTY = TeamUIPlugin.ID + ".DIRTY"; //$NON-NLS-1$
115 
116 	/*
117 	 * Key for settings in memento
118 	 */
119 	private static final String CTX_PARTICIPANT_SETTINGS = TeamUIPlugin.ID + ".MODEL_PARTICIPANT_SETTINGS"; //$NON-NLS-1$
120 
121 	/*
122 	 * Key for schedule in memento
123 	 */
124 	private static final String CTX_REFRESH_SCHEDULE_SETTINGS = TeamUIPlugin.ID + ".MODEL_PARTICIPANT_REFRESH_SCHEDULE"; //$NON-NLS-1$
125 
126 	/*
127 	 * Key for description in memento
128 	 */
129 	private static final String CTX_DESCRIPTION = TeamUIPlugin.ID + ".MODEL_PARTICIPANT_DESCRIPTION"; //$NON-NLS-1$
130 
131 	/*
132 	 * Constants used to save and restore this scope
133 	 */
134 	private static final String CTX_PARTICIPANT_MAPPINGS = TeamUIPlugin.ID + ".MODEL_PARTICIPANT_MAPPINGS"; //$NON-NLS-1$
135 	private static final String CTX_MODEL_PROVIDER_ID = "modelProviderId"; //$NON-NLS-1$
136 	private static final String CTX_MODEL_PROVIDER_MAPPINGS = "mappings"; //$NON-NLS-1$
137 	private static final String CTX_STARTUP_ACTION = "startupAction"; //$NON-NLS-1$
138 
139 	private SynchronizationContext context;
140 	private boolean mergingEnabled = true;
141 	private SubscriberRefreshSchedule refreshSchedule;
142 	private String description;
143 	private SaveableComparison activeSaveable;
144 	private PreferenceStore preferences = new PreferenceStore() {
145 		@Override
146 		public void save() throws IOException {
147 			// Nothing to do. Preference will be saved with participant.
148 		}
149 	};
150 
151 	private IPropertyListener dirtyListener = (source, propId) -> {
152 		if (source instanceof SaveableComparison && propId == SaveableComparison.PROP_DIRTY) {
153 			SaveableComparison scm = (SaveableComparison) source;
154 			boolean isDirty = scm.isDirty();
155 			firePropertyChange(ModelSynchronizeParticipant.this, PROP_DIRTY, Boolean.valueOf(!isDirty), Boolean.valueOf(isDirty));
156 		}
157 	};
158 
159 	/**
160 	 * Create a participant for the given context and name
161 	 * @param context the synchronization context
162 	 * @param name the name of the participant
163 	 * @return a participant for the given context
164 	 */
createParticipant(SynchronizationContext context, String name)165 	public static ModelSynchronizeParticipant createParticipant(SynchronizationContext context, String name) {
166 		return new ModelSynchronizeParticipant(context, name);
167 	}
168 
169 	/*
170 	 * Create a participant for the given context
171 	 * @param context the synchronization context
172 	 * @param name the name of the participant
173 	 */
ModelSynchronizeParticipant(SynchronizationContext context, String name)174 	private ModelSynchronizeParticipant(SynchronizationContext context, String name) {
175 		initializeContext(context);
176 		try {
177 			setInitializationData(TeamUI.getSynchronizeManager().getParticipantDescriptor("org.eclipse.team.ui.synchronization_context_synchronize_participant")); //$NON-NLS-1$
178 		} catch (CoreException e) {
179 			TeamUIPlugin.log(e);
180 		}
181 		setSecondaryId(Long.toString(System.currentTimeMillis()));
182 		setName(name);
183 		refreshSchedule = new SubscriberRefreshSchedule(createRefreshable());
184 	}
185 
186 	/**
187 	 * Create a participant for the given context
188 	 * @param context the synchronization context
189 	 */
ModelSynchronizeParticipant(SynchronizationContext context)190 	public ModelSynchronizeParticipant(SynchronizationContext context) {
191 		initializeContext(context);
192 		refreshSchedule = new SubscriberRefreshSchedule(createRefreshable());
193 	}
194 
195 	/**
196 	 * Create a participant in order to restore it from saved state.
197 	 */
ModelSynchronizeParticipant()198 	public ModelSynchronizeParticipant() {
199 	}
200 
201 	@Override
getName()202 	public String getName() {
203 		String name = super.getName();
204 		if (description == null)
205 			description = Utils.getScopeDescription(getContext().getScope());
206 		return NLS.bind(TeamUIMessages.SubscriberParticipant_namePattern, new String[] { name, description });
207 	}
208 
209 	/**
210 	 * Return the name of the participant as specified in the plugin manifest file.
211 	 * This method is provided to give access to this name since it is masked by
212 	 * the <code>getName()</code> method defined in this class.
213 	 * @return the name of the participant as specified in the plugin manifest file
214 	 * @since 3.1
215 	 */
getShortName()216 	protected final String getShortName() {
217 		return super.getName();
218 	}
219 
220 	@Override
initializeConfiguration( ISynchronizePageConfiguration configuration)221 	protected void initializeConfiguration(
222 			ISynchronizePageConfiguration configuration) {
223 		if (isMergingEnabled()) {
224 			// The context menu groups are defined by the org.eclipse.ui.navigator.viewer extension
225 			configuration.addMenuGroup(ISynchronizePageConfiguration.P_TOOLBAR_MENU, ModelSynchronizeParticipantActionGroup.MERGE_ACTION_GROUP);
226 			configuration.addActionContribution(createMergeActionGroup());
227 		}
228 		configuration.setSupportedModes(ISynchronizePageConfiguration.ALL_MODES);
229 		configuration.setMode(ISynchronizePageConfiguration.BOTH_MODE);
230 		configuration.setProperty(ITeamContentProviderManager.P_SYNCHRONIZATION_CONTEXT, getContext());
231 		configuration.setProperty(ITeamContentProviderManager.P_SYNCHRONIZATION_SCOPE, getContext().getScope());
232 		if (getHandler() != null)
233 			configuration.setProperty(StartupPreferencePage.STARTUP_PREFERENCES, preferences);
234 	}
235 
236 	/**
237 	 * Create the merge action group for this participant.
238 	 * Subclasses can override in order to provide a
239 	 * merge action group that configures certain aspects
240 	 * of the merge actions.
241 	 * @return the merge action group for this participant
242 	 */
createMergeActionGroup()243 	protected ModelSynchronizeParticipantActionGroup createMergeActionGroup() {
244 		return new ModelSynchronizeParticipantActionGroup();
245 	}
246 
247 	@Override
createPage( ISynchronizePageConfiguration configuration)248 	public final IPageBookViewPage createPage(
249 			ISynchronizePageConfiguration configuration) {
250 		return new ModelSynchronizePage(configuration);
251 	}
252 
253 	@Override
run(IWorkbenchPart part)254 	public void run(IWorkbenchPart part) {
255 		refresh(part != null ? part.getSite() : null, context.getScope().getMappings());
256 	}
257 
258 	/**
259 	 * Refresh a participant in the background the result of the refresh are shown in the progress view. Refreshing
260 	 * can also be considered synchronizing, or refreshing the synchronization state. Basically this is a long
261 	 * running operation that will update the participant's context with new changes detected on the
262 	 * server.
263 	 *
264 	 * @param site the workbench site the synchronize is running from. This can be used to notify the site
265 	 * that a job is running.
266 	 * @param mappings the resource mappings to be refreshed
267 	 */
refresh(IWorkbenchSite site, ResourceMapping[] mappings)268 	public final void refresh(IWorkbenchSite site, ResourceMapping[] mappings) {
269 		IRefreshSubscriberListener listener = new RefreshUserNotificationPolicy(this);
270 		internalRefresh(mappings, null, null, site, listener);
271 	}
272 
273 	@Override
dispose()274 	public void dispose() {
275 		context.dispose();
276 		Job.getJobManager().cancel(this);
277 		refreshSchedule.dispose();
278 	}
279 
280 	/**
281 	 * Set the context of this participant. This method must be invoked
282 	 * before a page is obtained from the participant.
283 	 * @param context the context for this participant
284 	 */
initializeContext(SynchronizationContext context)285 	protected void initializeContext(SynchronizationContext context) {
286 		this.context = context;
287 		mergingEnabled = context instanceof IMergeContext;
288 		SubscriberDiffTreeEventHandler handler = getHandler();
289 		if (handler != null) {
290 			preferences.setDefault(StartupPreferencePage.PROP_STARTUP_ACTION, StartupPreferencePage.STARTUP_ACTION_NONE);
291 			if (isSynchronizeOnStartup()) {
292 				run(null); // TODO: Would like to get the Sync view part if possible
293 			} else if (isPopulateOnStartup()) {
294 				handler.initializeIfNeeded();
295 			}
296 		}
297 	}
298 
isPopulateOnStartup()299 	private boolean isPopulateOnStartup() {
300 		String pref = preferences.getString(StartupPreferencePage.PROP_STARTUP_ACTION);
301 		return pref != null && pref.equals(StartupPreferencePage.STARTUP_ACTION_POPULATE);
302 	}
303 
isSynchronizeOnStartup()304 	private boolean isSynchronizeOnStartup() {
305 		String pref = preferences.getString(StartupPreferencePage.PROP_STARTUP_ACTION);
306 		return pref != null && pref.equals(StartupPreferencePage.STARTUP_ACTION_SYNCHRONIZE);
307 	}
308 
309 	/**
310 	 * Return the synchronization context for this participant.
311 	 * @return the synchronization context for this participant
312 	 */
getContext()313 	public ISynchronizationContext getContext() {
314 		return context;
315 	}
316 
317 	/**
318 	 * Return a compare input for the given model object or <code>null</code>
319 	 * if the object is not eligible for comparison.
320 	 * @param object the model object
321 	 * @return a compare input for the model object or <code>null</code>
322 	 */
asCompareInput(Object object)323 	public ICompareInput asCompareInput(Object object) {
324 		if (object instanceof ICompareInput) {
325 			return (ICompareInput) object;
326 		}
327 		// Get a compare input from the model provider's compare adapter
328 		ISynchronizationCompareAdapter adapter = Utils.getCompareAdapter(object);
329 		if (adapter != null)
330 			return adapter.asCompareInput(getContext(), object);
331 		return null;
332 	}
333 
334 	/**
335 	 * Return whether their is a compare input associated with the given object.
336 	 * In other words, return <code>true</code> if {@link #asCompareInput(Object) }
337 	 * would return a value and <code>false</code> if it would return <code>null</code>.
338 	 * @param object the object.
339 	 * @return whether their is a compare input associated with the given object
340 	 */
hasCompareInputFor(Object object)341 	public boolean hasCompareInputFor(Object object) {
342 		// Get a content viewer from the model provider's compare adapter
343 		ISynchronizationCompareAdapter adapter = Utils.getCompareAdapter(object);
344 		if (adapter != null)
345 			return adapter.hasCompareInput(getContext(), object);
346 		return false;
347 	}
348 
349 	/**
350 	 * Return whether merge capabilities are enabled for this participant.
351 	 * If merging is enabled, merge actions can be shown. If merging is disabled, no
352 	 * merge actions should be surfaced.
353 	 * @return whether merge capabilities should be enabled for this participant
354 	 */
isMergingEnabled()355 	public boolean isMergingEnabled() {
356 		return mergingEnabled;
357 	}
358 
359 	/**
360 	 * Set whether merge capabilities should be enabled for this participant.
361 	 * @param mergingEnabled whether merge capabilities should be enabled for this participant
362 	 */
setMergingEnabled(boolean mergingEnabled)363 	public void setMergingEnabled(boolean mergingEnabled) {
364 		this.mergingEnabled = mergingEnabled;
365 	}
366 
internalRefresh(ResourceMapping[] mappings, String jobName, String taskName, IWorkbenchSite site, IRefreshSubscriberListener listener)367 	private void internalRefresh(ResourceMapping[] mappings, String jobName, String taskName, IWorkbenchSite site, IRefreshSubscriberListener listener) {
368 		if (jobName == null)
369 			jobName = getShortTaskName();
370 		if (taskName == null)
371 			taskName = getLongTaskName(mappings);
372 		Job.getJobManager().cancel(this);
373 		RefreshParticipantJob job = new RefreshModelParticipantJob(this, jobName, taskName, mappings, listener);
374 		job.setUser(true);
375 		job.setProperty(IProgressConstants2.SHOW_IN_TASKBAR_ICON_PROPERTY, Boolean.TRUE);
376 		Utils.schedule(job, site);
377 
378 		// Remember the last participant synchronized
379 		TeamUIPlugin.getPlugin().getPreferenceStore().setValue(IPreferenceIds.SYNCHRONIZING_DEFAULT_PARTICIPANT, getId());
380 		TeamUIPlugin.getPlugin().getPreferenceStore().setValue(IPreferenceIds.SYNCHRONIZING_DEFAULT_PARTICIPANT_SEC_ID, getSecondaryId());
381 	}
382 
383 	/**
384 	 * Returns the short task name (e.g. no more than 25 characters) to describe
385 	 * the behavior of the refresh operation to the user. This is typically
386 	 * shown in the status line when this participant is refreshed in the
387 	 * background. When refreshed in the foreground, only the long task name is
388 	 * shown.
389 	 *
390 	 * @return the short task name to show in the status line.
391 	 */
getShortTaskName()392 	protected String getShortTaskName() {
393 		return NLS.bind(TeamUIMessages.Participant_synchronizingDetails, getShortName());
394 	}
395 
396 	/**
397 	 * Returns the long task name to describe the behavior of the refresh
398 	 * operation to the user. This is typically shown in the status line when
399 	 * this subscriber is refreshed in the background.
400 	 *
401 	 * @param mappings the mappings being refreshed
402 	 * @return the long task name
403 	 * @since 3.1
404 	 */
getLongTaskName(ResourceMapping[] mappings)405 	protected String getLongTaskName(ResourceMapping[] mappings) {
406 		if (mappings == null) {
407 			// If the mappings are null, assume we are refreshing everything
408 			mappings = getContext().getScope().getMappings();
409 		}
410 		int mappingCount = mappings.length;
411 		if (mappingCount == getContext().getScope().getMappings().length) {
412 			// Assume we are refreshing everything and only use the input mapping count
413 			mappings = getContext().getScope().getInputMappings();
414 			mappingCount = mappings.length;
415 		}
416 		if (mappingCount == 1) {
417 			return NLS.bind(TeamUIMessages.Participant_synchronizingMoreDetails, new String[] { getShortName(), Utils.getLabel(mappings[0]) });
418 		}
419 		return NLS.bind(TeamUIMessages.Participant_synchronizingResources, new String[] { getShortName(), Integer.toString(mappingCount) });
420 	}
421 
createRefreshable()422 	private IRefreshable createRefreshable() {
423 		return new IRefreshable() {
424 
425 			@Override
426 			public RefreshParticipantJob createJob(String interval) {
427 				String jobName = NLS.bind(TeamUIMessages.RefreshSchedule_15, new String[] { ModelSynchronizeParticipant.this.getName(), interval });
428 				return new RefreshModelParticipantJob(ModelSynchronizeParticipant.this,
429 						jobName,
430 						jobName,
431 						context.getScope().getMappings(),
432 						new RefreshUserNotificationPolicy(ModelSynchronizeParticipant.this));
433 			}
434 			@Override
435 			public ISynchronizeParticipant getParticipant() {
436 				return ModelSynchronizeParticipant.this;
437 			}
438 			@Override
439 			public void setRefreshSchedule(SubscriberRefreshSchedule schedule) {
440 				ModelSynchronizeParticipant.this.setRefreshSchedule(schedule);
441 			}
442 			@Override
443 			public SubscriberRefreshSchedule getRefreshSchedule() {
444 				return refreshSchedule;
445 			}
446 
447 		};
448 	}
449 
450 	@SuppressWarnings("unchecked")
451 	@Override
452 	public <T> T getAdapter(Class<T> adapter) {
453 		if (adapter == IRefreshable.class && refreshSchedule != null) {
454 			return (T) refreshSchedule.getRefreshable();
455 
456 		}
457 		if (adapter == SubscriberRefreshSchedule.class) {
458 			return (T) refreshSchedule;
459 		}
460 		return super.getAdapter(adapter);
461 	}
462 
463 	@Override
464 	public void saveState(IMemento memento) {
465 		super.saveState(memento);
466 		IMemento settings = memento.createChild(CTX_PARTICIPANT_SETTINGS);
467 		if (description != null)
468 			settings.putString(CTX_DESCRIPTION, description);
469 		refreshSchedule.saveState(settings.createChild(CTX_REFRESH_SCHEDULE_SETTINGS));
470 		saveMappings(settings);
471 		settings.putString(CTX_STARTUP_ACTION, preferences.getString(StartupPreferencePage.PROP_STARTUP_ACTION));
472 	}
473 
474 	private void saveMappings(IMemento settings) {
475 		ISynchronizationScope inputScope = getContext().getScope().asInputScope();
476 		ModelProvider[] providers = inputScope.getModelProviders();
477 		for (ModelProvider provider : providers) {
478 			ISynchronizationCompareAdapter adapter = Utils.getCompareAdapter(provider);
479 			if (adapter != null) {
480 				IMemento child = settings.createChild(CTX_PARTICIPANT_MAPPINGS);
481 				String id = provider.getDescriptor().getId();
482 				child.putString(CTX_MODEL_PROVIDER_ID, id);
483 				adapter.save(inputScope.getMappings(id), child.createChild(CTX_MODEL_PROVIDER_MAPPINGS));
484 			}
485 		}
486 	}
487 
488 	@Override
489 	public void init(String secondaryId, IMemento memento) throws PartInitException {
490 		super.init(secondaryId, memento);
491 		if(memento != null) {
492 			IMemento settings = memento.getChild(CTX_PARTICIPANT_SETTINGS);
493 			String startupAction = settings.getString(StartupPreferencePage.PROP_STARTUP_ACTION);
494 			if (startupAction != null)
495 				preferences.putValue(StartupPreferencePage.PROP_STARTUP_ACTION, startupAction);
496 			ResourceMapping[] mappings = loadMappings(settings);
497 			if (mappings.length == 0)
498 				throw new PartInitException(NLS.bind(TeamUIMessages.ModelSynchronizeParticipant_0, getId()));
499 			initializeContext(mappings);
500 			if(settings != null) {
501 				SubscriberRefreshSchedule schedule = SubscriberRefreshSchedule.init(settings.getChild(CTX_REFRESH_SCHEDULE_SETTINGS), createRefreshable());
502 				description = settings.getString(CTX_DESCRIPTION);
503 				setRefreshSchedule(schedule);
504 				if(schedule.isEnabled()) {
505 					schedule.startJob();
506 				}
507 			}
508 		}
509 	}
510 
511 	private ResourceMapping[] loadMappings(IMemento settings) throws PartInitException {
512 		List<ResourceMapping> result = new ArrayList<>();
513 		IMemento[] children = settings.getChildren(CTX_PARTICIPANT_MAPPINGS);
514 		for (IMemento memento : children) {
515 			String id = memento.getString(CTX_MODEL_PROVIDER_ID);
516 			if (id != null) {
517 				IModelProviderDescriptor desc = ModelProvider.getModelProviderDescriptor(id);
518 				try {
519 					ModelProvider provider = desc.getModelProvider();
520 					ISynchronizationCompareAdapter adapter = Utils.getCompareAdapter(provider);
521 					if (adapter != null) {
522 						ResourceMapping[] mappings = adapter.restore(memento.getChild(CTX_MODEL_PROVIDER_MAPPINGS));
523 						Collections.addAll(result, mappings);
524 					}
525 				} catch (CoreException e) {
526 					TeamUIPlugin.log(e);
527 				}
528 			}
529 		}
530 		return result.toArray(new ResourceMapping[result.size()]);
531 	}
532 
533 	private void initializeContext(ResourceMapping[] mappings) throws PartInitException {
534 		try {
535 			ISynchronizationScopeManager manager = createScopeManager(mappings);
536 			MergeContext context = restoreContext(manager);
537 			initializeContext(context);
538 		} catch (CoreException e) {
539 			TeamUIPlugin.log(e);
540 			throw new PartInitException(e.getStatus());
541 		}
542 	}
543 
544 	/**
545 	 * Recreate the context for this participant. This method is invoked when
546 	 * the participant is restored after a restart. Although it is provided
547 	 * with a progress monitor, long running operations should be avoided.
548 	 * @param manager the restored scope
549 	 * @return the context for this participant
550 	 * @throws CoreException if restoring context failed
551 	 */
552 	protected MergeContext restoreContext(ISynchronizationScopeManager manager) throws CoreException {
553 		throw new PartInitException(NLS.bind(TeamUIMessages.ModelSynchronizeParticipant_1, getId()));
554 	}
555 
556 	/**
557 	 * Create and return a scope manager that can be used to build the scope of this
558 	 * participant when it is restored after a restart. By default, this method
559 	 * returns a scope manager that uses the local content.
560 	 * This method can be overridden by subclasses.
561 	 *
562 	 * @param mappings the restored mappings
563 	 * @return a scope manager that can be used to build the scope of this
564 	 * participant when it is restored after a restart
565 	 */
566 	protected ISynchronizationScopeManager createScopeManager(ResourceMapping[] mappings) {
567 		return new SynchronizationScopeManager(super.getName(), mappings, ResourceMappingContext.LOCAL_CONTEXT, true);
568 	}
569 
570 	/* private */ void setRefreshSchedule(SubscriberRefreshSchedule schedule) {
571 		if (refreshSchedule != schedule) {
572 			if (refreshSchedule != null) {
573 				refreshSchedule.dispose();
574 			}
575 			this.refreshSchedule = schedule;
576 		}
577 		// Always fir the event since the schedule may have been changed
578 		firePropertyChange(this, AbstractSynchronizeParticipant.P_SCHEDULED, schedule, schedule);
579 	}
580 
581 	/**
582 	 * Return the active saveable for this participant.
583 	 * There is at most one saveable active at any
584 	 * time.
585 	 * @return the active saveable for this participant
586 	 * or <code>null</code>
587 	 */
588 	public SaveableComparison getActiveSaveable() {
589 		return activeSaveable;
590 	}
591 
592 	/**
593 	 * Set the active saveable of this participant.
594 	 * @param activeSaveable the active saveable (may be <code>null</code>)
595 	 */
596 	public void setActiveSaveable(SaveableComparison activeSaveable) {
597 		boolean wasDirty = false;
598 		SaveableComparison oldModel = this.activeSaveable;
599 		if (oldModel != null) {
600 			oldModel.removePropertyListener(dirtyListener);
601 			wasDirty = oldModel.isDirty();
602 		}
603 		this.activeSaveable = activeSaveable;
604 		firePropertyChange(this, PROP_ACTIVE_SAVEABLE, oldModel, activeSaveable);
605 		boolean isDirty = false;
606 		if (activeSaveable != null) {
607 			activeSaveable.addPropertyListener(dirtyListener);
608 			isDirty = activeSaveable.isDirty();
609 		}
610 		if (isDirty != wasDirty)
611 			firePropertyChange(this, PROP_DIRTY, Boolean.valueOf(wasDirty), Boolean.valueOf(isDirty));
612 	}
613 
614 	/**
615 	 * Convenience method for switching the active saveable of this participant
616 	 * to the saveable of the given input.
617 	 * @param shell a shell
618 	 * @param input the compare input about to be displayed
619 	 * @param cancelAllowed whether the display of the compare input can be canceled
620 	 * @param monitor a progress monitor or <code>null</code> if progress reporting is not required
621 	 * @return whether the user choose to continue with the display of the given compare input
622 	 * @throws CoreException if an error occurs
623 	 */
624 	public boolean checkForBufferChange(Shell shell, ISynchronizationCompareInput input, boolean cancelAllowed, IProgressMonitor monitor) throws CoreException {
625 		SaveableComparison currentBuffer = getActiveSaveable();
626 		SaveableComparison targetBuffer = input.getSaveable();
627 		if (monitor == null)
628 			monitor = new NullProgressMonitor();
629 		try {
630 			ModelParticipantAction.handleTargetSaveableChange(shell, targetBuffer, currentBuffer, cancelAllowed, Policy.subMonitorFor(monitor, 10));
631 		} catch (InterruptedException e) {
632 			return false;
633 		}
634 		setActiveSaveable(targetBuffer);
635 		return true;
636 	}
637 
638 	/**
639 	 * Return the list of model providers that are enabled for the participant.
640 	 * By default, the list is those model providers that contain mappings
641 	 * in the scope. Subclasses may override to add additional model providers.
642 	 * @return the list of model providers that are active for the participant
643 	 */
644 	public ModelProvider[] getEnabledModelProviders() {
645 		return getContext().getScope().getModelProviders();
646 	}
647 
648 	@Override
649 	public PreferencePage[] getPreferencePages() {
650 		List<PreferencePage> pages = new ArrayList<>();
651 		SyncViewerPreferencePage syncViewerPreferencePage = new SyncViewerPreferencePage();
652 		syncViewerPreferencePage.setIncludeDefaultLayout(false);
653 		pages.add(syncViewerPreferencePage);
654 		pages.add(new ModelEnablementPreferencePage());
655 		ITeamContentProviderDescriptor[] descriptors = TeamUI.getTeamContentProviderManager().getDescriptors();
656 		for (ITeamContentProviderDescriptor descriptor : descriptors) {
657 			if (isIncluded(descriptor)) {
658 				try {
659 					PreferencePage page = (PreferencePage)descriptor.createPreferencePage();
660 					if (page != null) {
661 						pages.add(page);
662 					}
663 				} catch (CoreException e) {
664 					TeamUIPlugin.log(e);
665 				}
666 			}
667 		}
668 		if (getHandler() != null) {
669 			pages.add(new StartupPreferencePage(preferences));
670 		}
671 		return pages.toArray(new PreferencePage[pages.size()]);
672 	}
673 
674 	private boolean isIncluded(ITeamContentProviderDescriptor descriptor) {
675 		ModelProvider[] providers = getEnabledModelProviders();
676 		for (ModelProvider provider : providers) {
677 			if (provider.getId().equals(descriptor.getModelProviderId())) {
678 				return true;
679 			}
680 		}
681 		return false;
682 	}
683 
684 	private SubscriberDiffTreeEventHandler getHandler() {
685 		return Adapters.adapt(context, SubscriberDiffTreeEventHandler.class);
686 	}
687 
688 }
689