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