| /******************************************************************************* |
| * Copyright (c) 2006, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.examples.model.mapping; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.mapping.ResourceMapping; |
| import org.eclipse.core.resources.mapping.ResourceTraversal; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.team.core.diff.FastDiffFilter; |
| import org.eclipse.team.core.diff.IDiff; |
| import org.eclipse.team.core.diff.IThreeWayDiff; |
| import org.eclipse.team.core.history.IFileRevision; |
| import org.eclipse.team.core.mapping.IMergeContext; |
| import org.eclipse.team.core.mapping.IResourceDiff; |
| import org.eclipse.team.core.mapping.ResourceMappingMerger; |
| import org.eclipse.team.core.mapping.provider.MergeStatus; |
| import org.eclipse.team.core.mapping.provider.ResourceDiffTree; |
| import org.eclipse.team.core.mapping.provider.SynchronizationContext; |
| import org.eclipse.team.examples.filesystem.FileSystemPlugin; |
| import org.eclipse.team.examples.model.ModelObject; |
| import org.eclipse.team.examples.model.ModelObjectDefinitionFile; |
| import org.eclipse.team.examples.model.ModelProject; |
| |
| /** |
| * A resource mapping merger for our example model |
| */ |
| public class ModelMerger extends ResourceMappingMerger { |
| |
| private final org.eclipse.team.examples.model.mapping.ExampleModelProvider provider; |
| |
| public ModelMerger(org.eclipse.team.examples.model.mapping.ExampleModelProvider provider) { |
| this.provider = provider; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.mapping.ResourceMappingMerger#getModelProvider() |
| */ |
| @Override |
| protected org.eclipse.core.resources.mapping.ModelProvider getModelProvider() { |
| return provider; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.mapping.ResourceMappingMerger#merge(org.eclipse.team.core.mapping.IMergeContext, org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| @Override |
| public IStatus merge(IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException { |
| try { |
| IStatus status; |
| // Only override the merge for three-way synchronizations |
| if (mergeContext.getType() == SynchronizationContext.THREE_WAY) { |
| monitor.beginTask("Merging model elements", 100); |
| status = mergeModelElements(mergeContext, SubMonitor.convert(monitor, 50)); |
| // Stop the merge if there was a failure |
| if (!status.isOK()) |
| return status; |
| // We need to wait for any background processing to complete for the context |
| // so the diff tree will be up-to-date when we delegate the rest of the merge |
| // to the superclass |
| try { |
| Job.getJobManager().join(mergeContext, SubMonitor.convert(monitor, 50)); |
| } catch (InterruptedException e) { |
| // Ignore |
| } |
| // Delegate the rest of the merge to the superclass |
| status = super.merge(mergeContext, monitor); |
| } else { |
| status = super.merge(mergeContext, monitor); |
| } |
| return status; |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /* |
| * Merge all the model element changes in the context |
| */ |
| private IStatus mergeModelElements(IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException { |
| try { |
| IDiff[] modeDiffs = getModDiffs(mergeContext); |
| List<IDiff> failures = new ArrayList<>(); |
| monitor.beginTask(null, 100 * modeDiffs.length); |
| for (IDiff diff : modeDiffs) { |
| if (!mergeModelElement(mergeContext, diff, SubMonitor.convert(monitor, 100))) { |
| failures.add(diff); |
| } |
| } |
| if (failures.size() > 0) { |
| return new MergeStatus(FileSystemPlugin.ID, "Several objects could not be merged", getMappings(failures)); |
| } |
| return Status.OK_STATUS; |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| private ResourceMapping[] getMappings(List failures) { |
| List<ResourceMapping> mappings = new ArrayList<>(); |
| for (Iterator iter = failures.iterator(); iter.hasNext();) { |
| IDiff diff = (IDiff) iter.next(); |
| IResource resource = ResourceDiffTree.getResourceFor(diff); |
| ModelObjectDefinitionFile file = (ModelObjectDefinitionFile)ModelObject.create(resource); |
| mappings.add(file.getAdapter(ResourceMapping.class)); |
| } |
| return mappings.toArray(new ResourceMapping[mappings.size()]); |
| } |
| |
| /* |
| * Return all the diffs for MOD files. |
| */ |
| private IDiff[] getModDiffs(IMergeContext mergeContext) { |
| final List<IDiff> result = new ArrayList<>(); |
| mergeContext.getDiffTree().accept(getModelProjectTraversals(mergeContext), diff -> { |
| IResource resource = ResourceDiffTree.getResourceFor(diff); |
| if (ModelObjectDefinitionFile.isModFile(resource)) { |
| result.add(diff); |
| } |
| return true; |
| }); |
| return result.toArray(new IDiff[result.size()]); |
| } |
| |
| /* |
| * Return a traversal that covers all the model projects in the scope of the merge. |
| */ |
| private ResourceTraversal[] getModelProjectTraversals(IMergeContext mergeContext) { |
| IProject[] scopeProjects = mergeContext.getScope().getProjects(); |
| List<IResource> modelProjects = new ArrayList<>(); |
| for (IProject project : scopeProjects) { |
| try { |
| if (ModelProject.isModProject(project)) { |
| modelProjects.add(project); |
| } |
| } catch (CoreException e) { |
| FileSystemPlugin.log(e); |
| } |
| } |
| if (modelProjects.isEmpty()) |
| return new ResourceTraversal[0]; |
| return new ResourceTraversal[] { |
| new ResourceTraversal(modelProjects.toArray(new IResource[modelProjects.size()]), |
| IResource.DEPTH_INFINITE, IResource.NONE) |
| }; |
| } |
| |
| /* |
| * Merge the model definition file and all the element files it contains. |
| */ |
| private boolean mergeModelElement(IMergeContext mergeContext, IDiff diff, IProgressMonitor monitor) throws CoreException { |
| if (diff instanceof IThreeWayDiff) { |
| IThreeWayDiff twd = (IThreeWayDiff) diff; |
| if (twd.getDirection() == IThreeWayDiff.INCOMING |
| || twd.getDirection() == IThreeWayDiff.CONFLICTING) { |
| IResource resource = ResourceDiffTree.getResourceFor(diff); |
| |
| // First, check if a change conflicts with a deletion |
| if (twd.getDirection() == IThreeWayDiff.CONFLICTING) { |
| if (!resource.exists()) |
| return false; |
| if (((IResourceDiff)twd.getRemoteChange()).getAfterState() == null) |
| return false; |
| } |
| |
| // First determine the element files and element file changes |
| IResourceDiff remoteChange = (IResourceDiff)twd.getRemoteChange(); |
| IResource[] localElements = getReferencedResources(resource); |
| IResource[] baseElements = getReferencedResources(resource.getProject().getName(), remoteChange.getBeforeState(), monitor); |
| IResource[] remoteElements = getReferencedResources(resource.getProject().getName(), remoteChange.getAfterState(), monitor); |
| IResource[] addedElements = getAddedElements(baseElements, remoteElements); |
| // Trick: The removed elements can be obtained by reversing the base and remote and looking for added |
| IResource[] removedElements = getAddedElements(remoteElements, baseElements); |
| |
| // Check to see if any removed elements have changed locally |
| if (hasOutgoingChanges(mergeContext, removedElements)) { |
| return false; |
| } |
| |
| // Now try to merge all the element files involved |
| Set<IResource> elementFiles = new HashSet<>(); |
| elementFiles.addAll(Arrays.asList(baseElements)); |
| elementFiles.addAll(Arrays.asList(localElements)); |
| elementFiles.addAll(Arrays.asList(remoteElements)); |
| if (!mergeElementFiles(mergeContext, elementFiles.toArray(new IResource[elementFiles.size()]), monitor)) { |
| return false; |
| } |
| |
| // Finally, merge the model definition |
| if (!resource.exists()) { |
| // This is a new model definition so just merge it |
| IStatus status = mergeContext.merge(diff, false, monitor); |
| if (!status.isOK()) |
| return false; |
| } else { |
| // Update the contents of the model definition file |
| ModelObjectDefinitionFile file = (ModelObjectDefinitionFile)ModelObject.create(resource); |
| elementFiles = new HashSet<>(); |
| elementFiles.addAll(Arrays.asList(localElements)); |
| elementFiles.addAll(Arrays.asList(addedElements)); |
| elementFiles.removeAll(Arrays.asList(removedElements)); |
| file.setElements(elementFiles.toArray(new IResource[elementFiles.size()])); |
| // Let the merge context know we handled the file |
| mergeContext.markAsMerged(diff, false, monitor); |
| } |
| } |
| } |
| return true; |
| } |
| |
| private boolean mergeElementFiles(IMergeContext mergeContext, IResource[] resources, IProgressMonitor monitor) throws CoreException { |
| IDiff[] diffs = getDiffs(mergeContext, resources); |
| IStatus status = mergeContext.merge(diffs, false, monitor); |
| return status.isOK(); |
| } |
| |
| private IDiff[] getDiffs(IMergeContext mergeContext, IResource[] resources) { |
| Set<IDiff> diffSet = new HashSet<>(); |
| for (IResource resource : resources) { |
| IDiff[] diffs = mergeContext.getDiffTree().getDiffs(resource, IResource.DEPTH_ZERO); |
| diffSet.addAll(Arrays.asList(diffs)); |
| } |
| return diffSet.toArray(new IDiff[diffSet.size()]); |
| } |
| |
| private boolean hasOutgoingChanges(IMergeContext mergeContext, IResource[] removedElements) { |
| FastDiffFilter fastDiffFilter = new FastDiffFilter() { |
| @Override |
| public boolean select(IDiff diff) { |
| if (diff instanceof IThreeWayDiff) { |
| IThreeWayDiff twd = (IThreeWayDiff) diff; |
| return twd.getDirection() == IThreeWayDiff.OUTGOING || twd.getDirection() == IThreeWayDiff.CONFLICTING; |
| } |
| return false; |
| } |
| }; |
| for (IResource resource : removedElements) { |
| if (mergeContext.getDiffTree().hasMatchingDiffs(resource.getFullPath(), fastDiffFilter)) |
| return true; |
| } |
| return false; |
| } |
| |
| private IResource[] getAddedElements(IResource[] baseElements, IResource[] remoteElements) { |
| List<IResource> result = new ArrayList<>(); |
| Set<IResource> base = new HashSet<>(); |
| for (IResource resource : baseElements) { |
| base.add(resource); |
| } |
| for (IResource resource : remoteElements) { |
| if (!base.contains(resource)) |
| result.add(resource); |
| } |
| return result.toArray(new IResource[result.size()]); |
| } |
| |
| private IResource[] getReferencedResources(IResource resource) throws CoreException { |
| if (resource instanceof IFile && resource.exists()) { |
| return ModelObjectDefinitionFile.getReferencedResources(resource.getProject().getName(), (IFile) resource); |
| } |
| return new IResource[0]; |
| } |
| |
| private IResource[] getReferencedResources(String projectName, IFileRevision revision, IProgressMonitor monitor) throws CoreException { |
| if (revision != null) { |
| return ModelObjectDefinitionFile.getReferencedResources(projectName, revision.getStorage(monitor)); |
| } |
| return new IResource[0]; |
| } |
| |
| } |