/*******************************************************************************
 * Copyright (C) 2011, 2012 Benjamin Muskalla <benjamin.muskalla@tasktop.com>
 * and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.egit.core.synchronize;

import static org.junit.Assert.assertTrue;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
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.ModelProvider;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.resources.mapping.ResourceMappingContext;
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.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.AdapterUtils;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.egit.core.test.GitTestCase;
import org.eclipse.egit.core.test.TestRepository;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.mapping.IMergeContext;
import org.eclipse.team.core.mapping.IMergeStatus;
import org.eclipse.team.core.mapping.IResourceMappingMerger;
import org.eclipse.team.core.mapping.ResourceMappingMerger;
import org.eclipse.team.core.mapping.provider.MergeStatus;
import org.eclipse.team.core.subscribers.SubscriberScopeManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class GitSubscriberMergeContextTest extends GitTestCase {

	private static final String MASTER = Constants.R_HEADS + Constants.MASTER;

	private static final String BRANCH = Constants.R_HEADS + "branch";

	private static final String SAMPLE_FILE_EXTENSION = "sample";

	private static final String SAMPLE_PROVIDER_ID = "egit.test.sample.provider";

	private Repository repo;

	private IProject iProject;

	private TestRepository testRepo;

	@Before
	public void setUp() throws Exception {
		super.setUp();

		iProject = project.project;
		testRepo = new TestRepository(gitDir);
		testRepo.connect(iProject);
		repo = RepositoryMapping.getMapping(iProject).getRepository();

		// make initial commit
		new Git(repo).commit().setAuthor("JUnit", "junit@jgit.org")
				.setMessage("Initial commit").call();

		IContentType textType = Platform.getContentTypeManager()
				.getContentType("org.eclipse.core.runtime.text");
		textType.addFileSpec(SAMPLE_FILE_EXTENSION,
				IContentType.FILE_EXTENSION_SPEC);
	}

	@After
	public void clearGitResources() throws Exception {
		testRepo.disconnect(iProject);
		testRepo.dispose();
		repo = null;
		super.tearDown();
	}

	@Test
	public void markAsMerged() throws Exception {
		String fileName = "src/Main.java";
		File file = testRepo.createFile(iProject, fileName);
		testRepo.appendContentAndCommit(iProject, file, "class Main {}",
				"some file");
		testRepo.addToIndex(iProject.getFile(".classpath"));
		testRepo.addToIndex(iProject.getFile(".project"));
		testRepo.commit("project files");

		IFile workspaceFile = testRepo.getIFile(iProject, file);

		testRepo.appendFileContent(file, "some changes");
		Status status = new Git(repo).status().call();
		assertEquals(0, status.getAdded().size());
		assertEquals(1, status.getModified().size());
		String repoRelativePath = testRepo.getRepoRelativePath(workspaceFile
				.getLocation().toPortableString());
		assertTrue(status.getModified().contains(repoRelativePath));

		iProject.refreshLocal(IResource.DEPTH_INFINITE,
				new NullProgressMonitor());

		IMergeContext mergeContext = prepareContext(workspaceFile, HEAD, HEAD);
		IDiff node = mergeContext.getDiffTree().getDiff(workspaceFile);
		assertNotNull(node);
		// First of all, "markAsMerged" is not supposed to have any effect on a
		// folder.
		// Second, it should only be used on IDiff obtained from the context,
		// not created for the occasion.
		mergeContext.markAsMerged(node, true, null);

		status = new Git(repo).status().call();
		assertEquals(1, status.getChanged().size());
		assertEquals(0, status.getModified().size());
		assertTrue(status.getChanged().contains(repoRelativePath));
	}

	@Test
	public void mergeNoConflict() throws Exception {
		String fileName = "src/Main.java";
		File file = testRepo.createFile(iProject, fileName);
		final String initialContent = "class Main {}\n";
		testRepo.appendContentAndCommit(iProject, file, initialContent,
				"some file");
		testRepo.addToIndex(iProject.getFile(".classpath"));
		testRepo.addToIndex(iProject.getFile(".project"));
		testRepo.commit("project files");

		IFile workspaceFile = testRepo.getIFile(iProject, file);
		String repoRelativePath = testRepo.getRepoRelativePath(workspaceFile
				.getLocation().toPortableString());

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		final String branchChanges = "branch changes\n";
		setContentsAndCommit(workspaceFile, branchChanges + initialContent,
				"branch commit");

		testRepo.checkoutBranch(MASTER);

		final String masterChanges = "some changes\n";
		setContentsAndCommit(workspaceFile, initialContent
				+ masterChanges, "master commit");
		iProject.refreshLocal(IResource.DEPTH_INFINITE,
				new NullProgressMonitor());
		// end setup

		IMergeContext mergeContext = prepareContext(workspaceFile, MASTER,
				BRANCH);
		IDiff node = mergeContext.getDiffTree().getDiff(workspaceFile);
		assertNotNull(node);

		IStatus mergeStatus = mergeContext.merge(node, false,
				new NullProgressMonitor());
		assertEquals(IStatus.OK, mergeStatus.getSeverity());
		assertContentEquals(workspaceFile, branchChanges + initialContent
				+ masterChanges);

		Status status = new Git(repo).status().call();
		assertEquals(1, status.getChanged().size());
		assertEquals(0, status.getModified().size());
		assertTrue(status.getChanged().contains(repoRelativePath));
	}

	@Test
	public void mergeModelNoConflict() throws Exception {
		File file1 = testRepo.createFile(iProject, "file1."
				+ SAMPLE_FILE_EXTENSION);
		File file2 = testRepo.createFile(iProject, "file2."
				+ SAMPLE_FILE_EXTENSION);

		String initialContent1 = "some content for the first file";
		String initialContent2 = "some content for the second file";
		testRepo.appendContentAndCommit(iProject, file1, initialContent1,
				"first file - initial commit");
		testRepo.appendContentAndCommit(iProject, file2, initialContent2,
				"second file - initial commit");

		IFile iFile1 = testRepo.getIFile(iProject, file1);
		IFile iFile2 = testRepo.getIFile(iProject, file2);
		String repoRelativePath1 = testRepo.getRepoRelativePath(iFile1
				.getLocation().toPortableString());
		String repoRelativePath2 = testRepo.getRepoRelativePath(iFile2
				.getLocation().toPortableString());

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		final String branchChanges = "branch changes\n";
		setContentsAndCommit(iFile1, branchChanges + initialContent1,
				"branch commit");
		setContentsAndCommit(iFile2, branchChanges + initialContent2,
				"branch commit");

		testRepo.checkoutBranch(MASTER);

		final String masterChanges = "some changes\n";
		setContentsAndCommit(iFile1, initialContent1 + masterChanges,
				"master commit");
		setContentsAndCommit(iFile2, initialContent2 + masterChanges,
				"master commit");
		iProject.refreshLocal(IResource.DEPTH_INFINITE,
				new NullProgressMonitor());
		// end setup

		IMergeContext mergeContext = prepareModelContext(iFile1, MASTER, BRANCH);
		IDiff node = mergeContext.getDiffTree().getDiff(iFile1);
		assertNotNull(node);
		node = mergeContext.getDiffTree().getDiff(iFile2);
		assertNotNull(node);

		IResourceMappingMerger merger = new SampleModelMerger(
				SAMPLE_PROVIDER_ID);
		IStatus mergeStatus = merger.merge(mergeContext,
				new NullProgressMonitor());
		assertEquals(IStatus.OK, mergeStatus.getSeverity());
		assertContentEquals(iFile1, branchChanges + initialContent1
				+ masterChanges);
		assertContentEquals(iFile2, branchChanges + initialContent2
				+ masterChanges);

		Status status = new Git(repo).status().call();
		assertEquals(2, status.getChanged().size());
		assertEquals(0, status.getModified().size());
		assertTrue(status.getChanged().contains(repoRelativePath1));
		assertTrue(status.getChanged().contains(repoRelativePath2));
	}

	@Test
	public void mergeWithConflict() throws Exception {
		String fileName = "src/Main.java";
		File file = testRepo.createFile(iProject, fileName);
		final String initialContent = "class Main {}\n";
		testRepo.appendContentAndCommit(iProject, file, initialContent,
				"some file");
		testRepo.addToIndex(iProject.getFile(".classpath"));
		testRepo.addToIndex(iProject.getFile(".project"));
		testRepo.commit("project files");

		IFile workspaceFile = testRepo.getIFile(iProject, file);
		String repoRelativePath = testRepo.getRepoRelativePath(workspaceFile
				.getLocation().toPortableString());

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		final String branchChanges = "branch changes\n";
		setContentsAndCommit(workspaceFile, initialContent + branchChanges,
				"branch commit");

		testRepo.checkoutBranch(MASTER);

		final String masterChanges = "some changes\n";
		setContentsAndCommit(workspaceFile, initialContent + masterChanges,
				"master commit");
		iProject.refreshLocal(IResource.DEPTH_INFINITE,
				new NullProgressMonitor());
		// end setup

		IMergeContext mergeContext = prepareContext(workspaceFile, MASTER,
				BRANCH);
		IDiff node = mergeContext.getDiffTree().getDiff(workspaceFile);
		assertNotNull(node);

		IStatus mergeStatus = mergeContext.merge(node, false,
				new NullProgressMonitor());
		assertEquals(IStatus.ERROR, mergeStatus.getSeverity());
		assertContentEquals(workspaceFile, initialContent + masterChanges);

		Status status = new Git(repo).status().call();
		assertEquals(0, status.getChanged().size());
		assertEquals(0, status.getModified().size());
		assertFalse(status.getChanged().contains(repoRelativePath));
	}

	@Test
	public void mergeModelWithConflict() throws Exception {
		File file1 = testRepo.createFile(iProject, "file1."
				+ SAMPLE_FILE_EXTENSION);
		File file2 = testRepo.createFile(iProject, "file2."
				+ SAMPLE_FILE_EXTENSION);

		String initialContent1 = "some content for the first file";
		String initialContent2 = "some content for the second file";
		testRepo.appendContentAndCommit(iProject, file1, initialContent1,
				"first file - initial commit");
		testRepo.appendContentAndCommit(iProject, file2, initialContent2,
				"second file - initial commit");

		IFile iFile1 = testRepo.getIFile(iProject, file1);
		IFile iFile2 = testRepo.getIFile(iProject, file2);

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		final String branchChanges = "branch changes\n";
		setContentsAndCommit(iFile1, initialContent1 + branchChanges,
				"branch commit");
		setContentsAndCommit(iFile2, initialContent2 + branchChanges,
				"branch commit");

		testRepo.checkoutBranch(MASTER);

		final String masterChanges = "some changes\n";
		setContentsAndCommit(iFile1, initialContent1 + masterChanges,
				"master commit");
		setContentsAndCommit(iFile2, initialContent2 + masterChanges,
				"master commit");
		iProject.refreshLocal(IResource.DEPTH_INFINITE,
				new NullProgressMonitor());
		// end setup

		IMergeContext mergeContext = prepareModelContext(iFile1, MASTER, BRANCH);
		IDiff node = mergeContext.getDiffTree().getDiff(iFile1);
		assertNotNull(node);
		node = mergeContext.getDiffTree().getDiff(iFile2);
		assertNotNull(node);

		IResourceMappingMerger merger = new SampleModelMerger(
				SAMPLE_PROVIDER_ID);
		IStatus mergeStatus = merger.merge(mergeContext,
				new NullProgressMonitor());
		assertEquals(IStatus.ERROR, mergeStatus.getSeverity());

		assertTrue(mergeStatus instanceof IMergeStatus);
		assertEquals(2,
				((IMergeStatus) mergeStatus).getConflictingMappings().length);
		Set<IFile> conflictingFiles = new LinkedHashSet<IFile>();
		for (ResourceMapping conflictingMapping : ((IMergeStatus) mergeStatus)
				.getConflictingMappings()) {
			assertTrue(conflictingMapping instanceof SampleResourceMapping
					&& conflictingMapping.getModelObject() instanceof IFile);
			conflictingFiles.add((IFile) conflictingMapping.getModelObject());
		}
		assertTrue(conflictingFiles.contains(iFile1));
		assertTrue(conflictingFiles.contains(iFile2));

		assertContentEquals(iFile1, initialContent1 + masterChanges);
		assertContentEquals(iFile2, initialContent2 + masterChanges);

		Status status = new Git(repo).status().call();
		assertEquals(0, status.getChanged().size());
		assertEquals(0, status.getModified().size());
	}

	@Test
	public void mergeModelWithDeletedFileNoConflict() throws Exception {
		File file1 = testRepo.createFile(iProject, "file1."
				+ SAMPLE_FILE_EXTENSION);
		File file2 = testRepo.createFile(iProject, "file2."
				+ SAMPLE_FILE_EXTENSION);

		String initialContent1 = "some content for the first file";
		String initialContent2 = "some content for the second file";
		testRepo.appendContentAndCommit(iProject, file1, initialContent1,
				"first file - initial commit");
		testRepo.appendContentAndCommit(iProject, file2, initialContent2,
				"second file - initial commit");

		IFile iFile1 = testRepo.getIFile(iProject, file1);
		IFile iFile2 = testRepo.getIFile(iProject, file2);
		String repoRelativePath1 = testRepo.getRepoRelativePath(iFile1
				.getLocation().toPortableString());
		String repoRelativePath2 = testRepo.getRepoRelativePath(iFile2
				.getLocation().toPortableString());

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		final String branchChanges = "branch changes\n";
		setContentsAndCommit(iFile1, branchChanges + initialContent1,
				"branch commit");
		iFile2.delete(true, new NullProgressMonitor());
		testRepo.addAndCommit(iProject, file2, "branch commit - deleted file2."
				+ SAMPLE_FILE_EXTENSION);

		testRepo.checkoutBranch(MASTER);

		final String masterChanges = "some changes\n";
		setContentsAndCommit(iFile1, initialContent1 + masterChanges,
				"master commit");
		iProject.refreshLocal(IResource.DEPTH_INFINITE,
				new NullProgressMonitor());
		// end setup

		IMergeContext mergeContext = prepareModelContext(iFile1, MASTER, BRANCH);
		IDiff node = mergeContext.getDiffTree().getDiff(iFile1);
		assertNotNull(node);
		node = mergeContext.getDiffTree().getDiff(iFile2);
		assertNotNull(node);

		IResourceMappingMerger merger = new SampleModelMerger(
				SAMPLE_PROVIDER_ID);
		IStatus mergeStatus = merger.merge(mergeContext,
				new NullProgressMonitor());
		assertEquals(IStatus.OK, mergeStatus.getSeverity());
		assertContentEquals(iFile1, branchChanges + initialContent1
				+ masterChanges);
		assertFalse(iFile2.exists());

		Status status = new Git(repo).status().call();
		assertEquals(1, status.getChanged().size());
		assertEquals(1, status.getRemoved().size());
		assertEquals(0, status.getModified().size());
		assertTrue(status.getChanged().contains(repoRelativePath1));
		assertTrue(status.getRemoved().contains(repoRelativePath2));
	}

	private RevCommit setContentsAndCommit(IFile targetFile,
			String newContents, String commitMessage) throws Exception {
		targetFile.setContents(
				new ByteArrayInputStream(newContents.getBytes()),
				IResource.FORCE, new NullProgressMonitor());
		testRepo.addToIndex(targetFile);
		return testRepo.commit(commitMessage);
	}

	private void assertContentEquals(IFile file, String expectedContents)
			throws Exception {
		BufferedReader reader = new BufferedReader(new InputStreamReader(
				file.getContents()));
		StringBuilder contentsBuilder = new StringBuilder();
		String line = reader.readLine();
		while (line != null) {
			contentsBuilder.append(line);
			contentsBuilder.append('\n');
			line = reader.readLine();
		}
		reader.close();
		assertEquals(expectedContents, contentsBuilder.toString());
	}

	private IMergeContext prepareContext(IFile workspaceFile, String srcRev,
			String dstRev) throws Exception {
		GitSynchronizeData gsd = new GitSynchronizeData(repo, srcRev, dstRev,
				true, Collections.<IResource> singleton(workspaceFile));
		GitSynchronizeDataSet gsds = new GitSynchronizeDataSet(gsd);
		GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
				gsds);
		subscriber.init(new NullProgressMonitor());

		ResourceMapping mapping = AdapterUtils.adapt(workspaceFile,
				ResourceMapping.class);
		SubscriberScopeManager manager = new SubscriberScopeManager(
				subscriber.getName(), new ResourceMapping[] { mapping, },
				subscriber, true);
		manager.initialize(new NullProgressMonitor());

		GitSubscriberMergeContext mergeContext = new GitSubscriberMergeContext(
				subscriber, manager, gsds);
		// Wait for asynchronous update of the diff tree to end
		Job.getJobManager().join(mergeContext, new NullProgressMonitor());
		return mergeContext;
	}

	private IMergeContext prepareModelContext(IFile workspaceFile,
			String srcRev,
			String dstRev) throws Exception {
		Set<IResource> includedResources = new HashSet<IResource>(
				Arrays.asList(workspaceFile));
		Set<IResource> newResources = new HashSet<IResource>(includedResources);
		Set<ResourceMapping> allMappings = new HashSet<ResourceMapping>();
		ResourceMappingContext mappingContext = ResourceMappingContext.LOCAL_CONTEXT;
		ModelProvider provider = new SampleProvider();
		do {
			Set<IResource> copy = newResources;
			newResources = new HashSet<IResource>();
			for (IResource resource : copy) {
				ResourceMapping[] mappings = provider.getMappings(resource,
						mappingContext, new NullProgressMonitor());
				allMappings.addAll(Arrays.asList(mappings));

				newResources.addAll(collectResources(mappings, mappingContext));
			}
		} while (includedResources.addAll(newResources));
		ResourceMapping[] mappings = allMappings
				.toArray(new ResourceMapping[allMappings.size()]);

		GitSynchronizeData gsd = new GitSynchronizeData(repo, srcRev, dstRev,
				true, includedResources);
		GitSynchronizeDataSet gsds = new GitSynchronizeDataSet(gsd);
		GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
				gsds);
		subscriber.init(new NullProgressMonitor());

		GitSubscriberResourceMappingContext resourceMappingContext = new GitSubscriberResourceMappingContext(
				subscriber, gsds);
		SubscriberScopeManager manager = new SubscriberScopeManager(
				subscriber.getName(), mappings, subscriber,
				resourceMappingContext, true);
		manager.initialize(new NullProgressMonitor());

		GitSubscriberMergeContext mergeContext = new GitSubscriberMergeContext(
				subscriber, manager, gsds);
		// Wait for asynchronous update of the diff tree to end
		Job.getJobManager().join(mergeContext, new NullProgressMonitor());
		return mergeContext;
	}

	private static Set<IResource> collectResources(ResourceMapping[] mappings,
			ResourceMappingContext mappingContext) throws Exception {
		final Set<IResource> resources = new HashSet<IResource>();
		for (ResourceMapping mapping : mappings) {
			ResourceTraversal[] traversals = mapping.getTraversals(
					mappingContext, new NullProgressMonitor());
			for (ResourceTraversal traversal : traversals)
				resources.addAll(Arrays.asList(traversal.getResources()));
		}
		return resources;
	}

	/**
	 * This model provider can be used to make all files of a given extension to
	 * be part of the same model. In this test, this will be used on files with
	 * extension {@link #SAMPLE_FILE_EXTENSION}.
	 */
	private static class SampleProvider extends ModelProvider {
		@Override
		public ResourceMapping[] getMappings(IResource resource,
				ResourceMappingContext context, IProgressMonitor monitor)
				throws CoreException {
			if (resource instanceof IFile
					&& SAMPLE_FILE_EXTENSION
							.equals(resource.getFileExtension())) {
				return new ResourceMapping[] { new SampleResourceMapping(
						(IFile) resource, SAMPLE_PROVIDER_ID), };
			}
			return super.getMappings(resource, context, monitor);
		}
	}

	/**
	 * This resource mapping will consider all files of extension
	 * {@link #SAMPLE_FILE_EXTENSION} in a given folder to be part of the same
	 * model.
	 */
	private static class SampleResourceMapping extends ResourceMapping {
		private final IFile file;

		private final String providerId;

		public SampleResourceMapping(IFile file, String providerId) {
			this.file = file;
			this.providerId = providerId;
		}

		@Override
		public Object getModelObject() {
			return file;
		}

		@Override
		public String getModelProviderId() {
			return providerId;
		}

		@Override
		public ResourceTraversal[] getTraversals(
				ResourceMappingContext context, IProgressMonitor monitor)
				throws CoreException {
			Set<IFile> sampleSiblings = new LinkedHashSet<IFile>();
			for (IResource res : file.getParent().members()) {
				if (res instanceof IFile
						&& SAMPLE_FILE_EXTENSION.equals(res.getFileExtension())) {
					sampleSiblings.add((IFile) res);
				}
			}
			final IResource[] resourceArray = sampleSiblings
					.toArray(new IResource[sampleSiblings.size()]);
			return new ResourceTraversal[] { new ResourceTraversal(
					resourceArray, IResource.DEPTH_ONE, IResource.NONE), };
		}

		@Override
		public IProject[] getProjects() {
			return new IProject[] { file.getProject(), };
		}
	}

	/**
	 * Since we do not register our model provider the plugin way, we won't have
	 * a descriptor for it, and the default implementation would fail. This will
	 * avoid all the useless calls to provider.getDescriptor().
	 */
	private static class SampleModelMerger extends ResourceMappingMerger {
		private final String providerID;

		public SampleModelMerger(String providerID) {
			this.providerID = providerID;
		}

		@Override
		protected ModelProvider getModelProvider() {
			return null;
		}

		@Override
		public IStatus merge(IMergeContext mergeContext,
				IProgressMonitor monitor) throws CoreException {
			IDiff[] deltas = getSetToMerge(mergeContext);
			IStatus status = mergeContext.merge(deltas, false /* don't force */,
					monitor);
			if (status.getCode() == IMergeStatus.CONFLICTS) {
				return new MergeStatus(status.getPlugin(), status.getMessage(),
						mergeContext.getScope().getMappings(providerID));
			}
			return status;
		}

		private IDiff[] getSetToMerge(IMergeContext mergeContext) {
			ResourceMapping[] mappings = mergeContext.getScope().getMappings(
					providerID);
			Set<IDiff> deltas = new HashSet<IDiff>();
			for (int i = 0; i < mappings.length; i++) {
				ResourceTraversal[] traversals = mergeContext.getScope()
						.getTraversals(mappings[i]);
				deltas.addAll(Arrays.asList(mergeContext.getDiffTree()
						.getDiffs(traversals)));
			}
			return deltas.toArray(new IDiff[deltas.size()]);
		}
	}
}
