<commit_before>/*******************************************************************************
 * Copyright (c) 2008-2011 Chair for Applied Software Engineering,
 * Technische Universitaet Muenchen.
 * 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
 * 
 * Contributors:
 ******************************************************************************/
package org.eclipse.emf.emfstore.client.model.changeTracking.merging;

import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isAttribute;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isComposite;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isCompositeRef;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isDelete;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isDiagramLayout;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiAtt;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiAttMove;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiAttSet;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiRef;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiRefSet;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isSingleRef;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.Conflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.AttributeConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.CompositeConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.DeletionConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.DiagramLayoutConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeMoveConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeMoveSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeSetSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSetSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSetSingleConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSingleConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.ReferenceConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.SingleReferenceConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.util.DecisionUtil;
import org.eclipse.emf.emfstore.client.model.exceptions.ChangeConflictException;
import org.eclipse.emf.emfstore.common.extensionpoint.ExtensionElement;
import org.eclipse.emf.emfstore.common.extensionpoint.ExtensionPoint;
import org.eclipse.emf.emfstore.common.model.ModelElementId;
import org.eclipse.emf.emfstore.common.model.Project;
import org.eclipse.emf.emfstore.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.server.conflictDetection.ConflictBucket;
import org.eclipse.emf.emfstore.server.conflictDetection.ConflictBucketCandidate;
import org.eclipse.emf.emfstore.server.conflictDetection.ConflictDetector;
import org.eclipse.emf.emfstore.server.model.versioning.ChangePackage;
import org.eclipse.emf.emfstore.server.model.versioning.LogMessage;
import org.eclipse.emf.emfstore.server.model.versioning.PrimaryVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.impl.ChangePackageImpl;
import org.eclipse.emf.emfstore.server.model.versioning.operations.AbstractOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.CompositeOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.CreateDeleteOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.MultiAttributeOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.MultiReferenceOperation;

/**
 * DecisionManager is the controller for the merge dialog and therefore it's
 * main component. It calculates the conflicts from incoming changes and can
 * execute resolved conflicts.
 * 
 * @author wesendon
 */
public class DecisionManager {

	private final Project project;
	private final List<ChangePackage> myChangePackages;
	private final List<ChangePackage> theirChangePackages;
	private ConflictDetector conflictDetector;

	private ArrayList<Conflict> conflicts;
	private Set<AbstractOperation> notInvolvedInConflict;
	private ArrayList<AbstractOperation> acceptedMine;
	private ArrayList<AbstractOperation> rejectedTheirs;
	private final PrimaryVersionSpec baseVersion;
	private final PrimaryVersionSpec targetVersion;
	private List<ConflictHandler> conflictHandler;
	private final boolean isBranchMerge;
	private ChangeConflictException conflictException;

	/**
	 * Default constructor.
	 * 
	 * @param project
	 *            the related project
	 * @param myChangePackages
	 *            my changes
	 * @param theirChangePackages
	 *            incoming changes
	 * @param baseVersion
	 *            baseversion
	 * @param targetVersion
	 *            new target version
	 * @param isBranchMerge
	 *            allows to specify whether two branches are merged, opposed to
	 *            changes from the same branch. Has an effect on the wording of
	 *            conflictions
	 * @param conflictException a conflict exception with preliminary results
	 */
	public DecisionManager(Project project, List<ChangePackage> myChangePackages,
		List<ChangePackage> theirChangePackages, PrimaryVersionSpec baseVersion, PrimaryVersionSpec targetVersion,
		boolean isBranchMerge, ChangeConflictException conflictException) {
		this.project = project;
		this.myChangePackages = myChangePackages;
		this.theirChangePackages = theirChangePackages;
		this.baseVersion = baseVersion;
		this.targetVersion = targetVersion;
		this.isBranchMerge = isBranchMerge;
		this.conflictException = conflictException;
		this.conflictHandler = initConflictHandlers();
		this.conflictDetector = new ConflictDetector();
		init();
	}

	private List<ConflictHandler> initConflictHandlers() {
		ArrayList<ConflictHandler> result = new ArrayList<ConflictHandler>();
		for (ExtensionElement element : new ExtensionPoint("org.eclipse.emf.emfstore.client.merge.conflictHandler")
			.getExtensionElements()) {
			ConflictHandler handler = element.getClass("class", ConflictHandler.class);
			if (handler != null) {
				result.add(handler);
			}
		}
		return result;
	}

	private void init() {
		// flatten operations

		acceptedMine = new ArrayList<AbstractOperation>();
		rejectedTheirs = new ArrayList<AbstractOperation>();

		conflicts = new ArrayList<Conflict>();
		notInvolvedInConflict = new LinkedHashSet<AbstractOperation>();

		Set<ConflictBucketCandidate> conflictBucketCandidates;
		if (conflictException != null) {
			conflictBucketCandidates = conflictException.getConflictBucketCandidates();
		} else {
			conflictBucketCandidates = new ConflictDetector().calculateConflictCandidateBuckets(myChangePackages,
				theirChangePackages);
		}
		Set<ConflictBucket> conflictBucketsSet = conflictDetector.calculateConflictBucketsFromConflictCandidateBuckets(
			conflictBucketCandidates, notInvolvedInConflict);

		createConflicts(conflictBucketsSet);
	}

	/**
	 * BEGIN FACTORY TODO EXTRACT FACTORY CLASS.
	 */

	// BEGIN COMPLEX CODE
	private void createConflicts(Set<ConflictBucket> ConflictBucket) {
		// Create Conflicts from ConflictBucket
		for (ConflictBucket conf : ConflictBucket) {
			AbstractOperation my = conf.getMyOperation();
			AbstractOperation their = conf.getTheirOperation();

			// #checkRegistedHandlers adds Conflicts on its own
			if (checkRegisteredHandlers(conf)) {
				continue;

			} else if (isDiagramLayout(my) && isDiagramLayout(their)) {

				addConflict(createDiagramLayoutDecision(conf));
				continue;

			} else if (isAttribute(my) && isAttribute(their)) {

				addConflict(createAttributeAttributeDecision(conf));
				continue;

			} else if (isSingleRef(my) && isSingleRef(their)) {

				addConflict(createSingleSingleConflict(conf));
				continue;

			} else if (isMultiRef(my) && isMultiRef(their)) {

				addConflict(createMultiMultiConflict(conf));
				continue;

			} else if ((isMultiRef(my) && isSingleRef(their)) || (isMultiRef(their) && isSingleRef(my))) {

				addConflict(createMultiSingle(conf));
				continue;

			} else if (isCompositeRef(my) && isCompositeRef(their)) {

				addConflict(createReferenceConflict(conf));
				continue;

			} else if ((isCompositeRef(my) && (isMultiRef(their) || isSingleRef(their)))
				|| ((isMultiRef(my) || isSingleRef(my)) && isCompositeRef(their))) {

				addConflict(createReferenceCompVSSingleMulti(conf));
				continue;

			} else if ((isMultiRef(my) && isMultiRefSet(their)) || (isMultiRef(their) && isMultiRefSet(my))) {

				addConflict(createMultiRefMultiSet(conf));
				continue;

			} else if (isMultiRefSet(my) && isMultiRefSet(their)) {

				addConflict(createMultiRefSetSet(conf));
				continue;

			} else if ((isMultiRefSet(my) && isSingleRef(their)) || (isMultiRefSet(their) && isSingleRef(my))) {

				addConflict(createMultiSetSingle(conf));
				continue;

			} else if (isMultiAtt(my) && isMultiAtt(their)) {

				addConflict(createMultiAtt(conf));
				continue;

			} else if ((isMultiAtt(my) && isMultiAttSet(their)) || (isMultiAtt(their) && isMultiAttSet(my))) {

				addConflict(createMultiAttSet(conf));
				continue;

			} else if ((isMultiAtt(my) && isMultiAttMove(their)) || (isMultiAtt(their) && isMultiAttMove(my))) {

				addConflict(createMultiAttMove(conf));
				continue;

			} else if ((isMultiAttSet(my) && isMultiAttMove(their)) || (isMultiAttSet(their) && isMultiAttMove(my))) {

				addConflict(createMultiAttMoveSet(conf));
				continue;

			} else if (isMultiAttSet(my) && isMultiAttSet(their)) {

				addConflict(createMultiAttSetSet(conf));
				continue;

			} else if (isComposite(my) || isComposite(their)) {

				addConflict(createCompositeConflict(conf));
				continue;

			} else if (isDelete(my) || isDelete(their)) {

				addConflict(createDeleteOtherConflict(conf));

			}
		}
	}

	private boolean checkRegisteredHandlers(ConflictBucket conf) {
		for (ConflictHandler handler : this.conflictHandler) {
			if (handler.canHandle(conf)) {
				addConflict(handler.handle(this, conf));
				return true;
			}
		}
		return false;
	}

	private void addConflict(Conflict conflict) {
		if (conflict == null) {
			return;
		}
		conflicts.add(conflict);
	}

	// END COMPLEX CODE
	private Conflict createMultiRefMultiSet(ConflictBucket conf) {
		if (isMultiRef(conf.getMyOperation())) {
			return new MultiReferenceSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceSetConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiSetSingle(ConflictBucket conf) {
		if (isMultiRefSet(conf.getMyOperation())) {
			return new MultiReferenceSetSingleConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceSetSingleConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiSingle(ConflictBucket conf) {
		if (isMultiRef(conf.getMyOperation())) {
			return new MultiReferenceSingleConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceSingleConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiRefSetSet(ConflictBucket conf) {
		return new MultiReferenceSetSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
			conf.getMyOperation(), conf.getTheirOperation(), this);
	}

	private Conflict createMultiAttSetSet(ConflictBucket conf) {
		return new MultiAttributeSetSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
			conf.getMyOperation(), conf.getTheirOperation(), this);
	}

	private Conflict createMultiAtt(ConflictBucket conf) {
		if (((MultiAttributeOperation) conf.getMyOperation()).isAdd()) {
			return new MultiAttributeConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);

		}
	}

	private Conflict createMultiAttSet(ConflictBucket conf) {
		if (isMultiAtt(conf.getMyOperation())) {
			return new MultiAttributeSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeSetConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiAttMove(ConflictBucket conf) {
		if (isMultiAtt(conf.getMyOperation())) {
			return new MultiAttributeMoveConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeMoveConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiAttMoveSet(ConflictBucket conf) {
		if (isMultiAttSet(conf.getMyOperation())) {
			return new MultiAttributeMoveSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeMoveSetConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createReferenceCompVSSingleMulti(ConflictBucket conf) {
		if (isCompositeRef(conf.getMyOperation())) {
			return createRefFromSub(conf, ((CompositeOperation) conf.getMyOperation()).getSubOperations(),
				Arrays.asList(conf.getTheirOperation()));
		} else {
			return createRefFromSub(conf, Arrays.asList(conf.getMyOperation()),
				((CompositeOperation) conf.getTheirOperation()).getSubOperations());
		}
	}

	private Conflict createReferenceConflict(ConflictBucket conf) {
		EList<AbstractOperation> myOperations = ((CompositeOperation) conf.getMyOperation()).getSubOperations();
		EList<AbstractOperation> theirOperations = ((CompositeOperation) conf.getTheirOperation()).getSubOperations();

		return createRefFromSub(conf, myOperations, theirOperations);
	}

	private Conflict createRefFromSub(ConflictBucket conf, List<AbstractOperation> myOperations,
		List<AbstractOperation> theirOperations) {

		for (AbstractOperation myOp : myOperations) {
			for (AbstractOperation theirOp : theirOperations) {
				if (conflictDetector.doConflict(myOp, theirOp)) {
					if (isSingleRef(myOp)) {

						return new ReferenceConflict(createSingleSingleConflict(myOp, theirOp), conf.getMyOperations(),
							conf.getTheirOperations(), conf.getMyOperation(), conf.getTheirOperation());

					} else if (isMultiRef(myOp)) {

						return new ReferenceConflict(createMultiMultiConflict(myOp, theirOp), conf.getMyOperations(),
							conf.getTheirOperations(), conf.getMyOperation(), conf.getTheirOperation());

					} else {
						return null;
					}
				}
			}
		}
		return null;
	}

	private Conflict createAttributeAttributeDecision(ConflictBucket conf) {
		return new AttributeConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
			conf.getTheirOperation(), this);
	}

	private Conflict createDiagramLayoutDecision(ConflictBucket conf) {
		return new DiagramLayoutConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
			conf.getTheirOperation(), this);
	}

	private Conflict createSingleSingleConflict(ConflictBucket conf) {
		return new SingleReferenceConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
			conf.getTheirOperation(), this);
	}

	private Conflict createSingleSingleConflict(AbstractOperation my, AbstractOperation their) {
		return new SingleReferenceConflict(Collections.singleton(my), Collections.singleton(their), my, their, this);
	}

	private Conflict createMultiMultiConflict(ConflictBucket conf) {
		if (((MultiReferenceOperation) conf.getMyOperation()).isAdd()) {
			return new MultiReferenceConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, false);
		}
	}

	private Conflict createMultiMultiConflict(AbstractOperation my, AbstractOperation their) {
		if (((MultiReferenceOperation) my).isAdd()) {
			return new MultiReferenceConflict(Collections.singleton(my), Collections.singleton(their), my, their, this,
				true);
		} else {
			return new MultiReferenceConflict(Collections.singleton(their), Collections.singleton(my), their, my, this,
				false);
		}
	}

	private Conflict createDeleteOtherConflict(ConflictBucket conf) {
		if (isDelete(conf.getMyOperation())) {
			return new DeletionConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), true, this);
		} else {
			return new DeletionConflict(conf.getTheirOperations(), conf.getMyOperations(), conf.getTheirOperation(),
				conf.getMyOperation(), false, this);
		}
	}

	private Conflict createCompositeConflict(ConflictBucket conf) {
		if (isComposite(conf.getMyOperation())) {
			return new CompositeConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, true);
		} else {
			return new CompositeConflict(conf.getTheirOperations(), conf.getMyOperations(), conf.getTheirOperation(),
				conf.getMyOperation(), this, false);
		}
	}

	/**
	 * FACTORY END
	 */

	/**
	 * Returns the conflicts.
	 * 
	 * @return list of conflicts.
	 */
	public ArrayList<Conflict> getConflicts() {
		return conflicts;
	}

	/**
	 * Checks whether all conflicts are resolved.
	 * 
	 * @return true if all are resolved
	 */
	public boolean isResolved() {
		boolean isResolved = true;
		for (Conflict conflict : conflicts) {
			isResolved = isResolved && conflict.isResolved();
		}
		return isResolved;
	}

	/**
	 * Get "my" accepted operations. This list will be empty, if {@link #calcResult()} hasn't been called before.
	 * 
	 * @return list of operations
	 */
	public List<AbstractOperation> getAcceptedMine() {
		return acceptedMine;
	}

	/**
	 * Get "their" accepted operations. This list will be empty, if {@link #calcResult()} hasn't been called before.
	 * 
	 * @return list of operations
	 */
	public List<AbstractOperation> getRejectedTheirs() {
		return rejectedTheirs;
	}

	/**
	 * If all conflicts are resolved this method will generate the resulting
	 * operations from the conflicts. Then call {@link #getAcceptedMine()} and {@link #getRejectedTheirs()}.
	 */
	// BEGIN COMPLEX CODE
	public void calcResult() {
		if (!isResolved()) {
			return;
		}

		Set<AbstractOperation> accceptedMineSet = new LinkedHashSet<AbstractOperation>();
		Set<AbstractOperation> rejectedTheirsSet = new LinkedHashSet<AbstractOperation>();

		for (Conflict conflict : conflicts) {
			accceptedMineSet.addAll(conflict.getAcceptedMine());
			rejectedTheirsSet.addAll(conflict.getRejectedTheirs());
		}

		// collect my accepted operations
		for (ChangePackage myChangePackage : myChangePackages) {
			for (AbstractOperation myOp : myChangePackage.getOperations()) {
				if (notInvolvedInConflict.contains(myOp)) {
					acceptedMine.add(myOp);
				} else if (accceptedMineSet.contains(myOp)) {
					acceptedMine.add(myOp);
				}
				accceptedMineSet.remove(myOp);
			}
		}

		// add all remaining operations in acceptedMineSet (they have been generated during merge)
		acceptedMine.addAll(accceptedMineSet);

		for (ChangePackage theirCP : theirChangePackages) {
			for (AbstractOperation theirOp : theirCP.getOperations()) {
				if (rejectedTheirsSet.contains(theirOp)) {
					rejectedTheirs.add(theirOp);
				}
			}
		}
	}

	// END COMPLEX CODE

	/**
	 * Returns the conflictdetector.
	 * 
	 * @return conflictdetector
	 */
	public ConflictDetector getConflictDetector() {
		return conflictDetector;
	}

	/**
	 * Flat whether branches are merged opposed to versions on the same branch.
	 * 
	 * @return true, if branches
	 */
	public boolean isBranchMerge() {
		return isBranchMerge;
	}

	/**
	 * Get the Name of an model element by modelelement id.
	 * 
	 * @param modelElementId
	 *            id of element
	 * @return name as string
	 */
	public String getModelElementName(ModelElementId modelElementId) {
		return getModelElementName(getModelElement(modelElementId));
	}

	/**
	 * Get the Name of an model element.
	 * 
	 * @param modelElement
	 *            element
	 * @return name as string
	 */
	public String getModelElementName(EObject modelElement) {
		return DecisionUtil.getModelElementName(modelElement);
	}

	/**
	 * Returns the modelelement. Therefore the project as well as creation and
	 * deletion operations are searched.
	 * 
	 * @param modelElementId
	 *            id of element.
	 * @return modelelement
	 */
	// TODO: SLOW!
	public EObject getModelElement(ModelElementId modelElementId) {
		EObject modelElement = project.getModelElement(modelElementId);
		if (modelElement == null) {
			for (ChangePackage cp : myChangePackages) {
				modelElement = searchForCreatedME(modelElementId, cp.getOperations());
				if (modelElement != null) {
					break;
				}
			}
			if (modelElement == null) {
				for (ChangePackage cp : theirChangePackages) {
					modelElement = searchForCreatedME(modelElementId, cp.getOperations());
					if (modelElement != null) {
						break;
					}
				}
			}
		}
		return modelElement;
	}

	private EObject searchForCreatedME(ModelElementId modelElementId, List<AbstractOperation> operations) {
		for (AbstractOperation operation : operations) {
			EObject result = null;
			if (operation instanceof CreateDeleteOperation) {
				result = searchCreateAndDelete((CreateDeleteOperation) operation, modelElementId);

			} else if (operation instanceof CompositeOperation) {
				EList<AbstractOperation> subOperations = ((CompositeOperation) operation).getSubOperations();
				result = searchForCreatedME(modelElementId, subOperations);
			} else {
				continue;
			}
			if (result != null) {
				return result;
			}
		}
		return null;
	}

	private EObject searchCreateAndDelete(CreateDeleteOperation cdo, ModelElementId modelElementId) {
		EObject modelElement = cdo.getModelElement();
		if (modelElement == null) {
			return null;
		}
		Set<EObject> containedModelElements = ModelUtil.getAllContainedModelElements(modelElement, false);
		containedModelElements.add(modelElement);

		for (EObject child : containedModelElements) {
			ModelElementId childId = ModelUtil.clone(cdo.getEObjectToIdMap().get(child));
			if (childId != null && childId.equals(modelElementId)) {
				return child;
			}
		}
		return null;
	}

	/**
	 * Returns the name of the author for a operation in list of their
	 * operations.
	 * 
	 * @param theirOperation
	 *            operation
	 * @return name as string or ""
	 */
	public String getAuthorForOperation(AbstractOperation theirOperation) {
		for (ChangePackage cp : theirChangePackages) {
			for (AbstractOperation op : cp.getOperations()) {
				List<AbstractOperation> tmpList = new ArrayList<AbstractOperation>();
				if (op instanceof CompositeOperation) {
					tmpList.add(op);
					tmpList.addAll(((CompositeOperation) op).getSubOperations());
				} else {
					tmpList.add(op);
				}
				for (AbstractOperation ao : tmpList) {
					if (ao.equals(theirOperation)) {
						LogMessage log = cp.getLogMessage();
						if (log == null) {
							return "";
						}
						return (log.getAuthor() == null) ? "" : log.getAuthor();

					}
				}
			}
		}
		return "";
	}

	/**
	 * Return the related project.
	 * 
	 * @return project
	 */
	public Project getProject() {
		return project;
	}

	/**
	 * Returns the baseVersion of the project which is updating.
	 * 
	 * @return version
	 */
	public PrimaryVersionSpec getBaseVersion() {
		return baseVersion;
	}

	/**
	 * Returns the targetVersion of the update which caused merging.
	 * 
	 * @return version
	 */
	public PrimaryVersionSpec getTargetVersion() {
		return targetVersion;
	}

	/**
	 * Gives Access to internal values. Use with care.
	 */
	// BEGIN COMPLEX CODE
	public DecisionManager.Internal Internal = this.new Internal();

	// END COMPLEX CODE

	/**
	 * This class allows access to internal values. Use with care.
	 * 
	 * @author wesendon
	 */
	public class Internal {
		/**
		 * My CP.
		 * 
		 * @return list of cp
		 */
		public List<ChangePackage> getMyChangePackages() {
			return myChangePackages;
		}

		/**
		 * Their CP.
		 * 
		 * @return list of cp
		 */
		public List<ChangePackage> getTheirChangePackages() {
			return theirChangePackages;
		}
	}

	private Integer myLeafOperationCount;

	/**
	 * Count my leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countMyLeafOperations() {
		if (myLeafOperationCount == null) {
			myLeafOperationCount = ChangePackageImpl.countLeafOperations(myChangePackages);
		}
		return myLeafOperationCount;
	}

	private Integer theirLeafOperationCount;

	/**
	 * Count their leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countTheirLeafOperations() {
		if (theirLeafOperationCount == null) {
			theirLeafOperationCount = ChangePackageImpl.countLeafOperations(theirChangePackages);
		}
		return theirLeafOperationCount;
	}

	private Integer myOperationCount;

	/**
	 * Count my leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countMyOperations() {
		if (myOperationCount == null) {
			myOperationCount = ChangePackageImpl.countOperations(myChangePackages);
		}
		return myOperationCount;
	}

	private Integer theirOperationCount;

	/**
	 * Count their leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countTheirOperations() {
		if (theirOperationCount == null) {
			theirOperationCount = ChangePackageImpl.countOperations(theirChangePackages);
		}
		return theirOperationCount;
	}

}
<commit_msg>count conflicting operations instead of all opeations<commit_after>/*******************************************************************************
 * Copyright (c) 2008-2011 Chair for Applied Software Engineering,
 * Technische Universitaet Muenchen.
 * 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
 * 
 * Contributors:
 ******************************************************************************/
package org.eclipse.emf.emfstore.client.model.changeTracking.merging;

import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isAttribute;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isComposite;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isCompositeRef;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isDelete;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isDiagramLayout;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiAtt;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiAttMove;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiAttSet;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiRef;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isMultiRefSet;
import static org.eclipse.emf.emfstore.server.model.versioning.operations.util.OperationUtil.isSingleRef;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.Conflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.AttributeConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.CompositeConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.DeletionConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.DiagramLayoutConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeMoveConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeMoveSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiAttributeSetSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSetSetConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSetSingleConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.MultiReferenceSingleConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.ReferenceConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.conflict.conflicts.SingleReferenceConflict;
import org.eclipse.emf.emfstore.client.model.changeTracking.merging.util.DecisionUtil;
import org.eclipse.emf.emfstore.client.model.exceptions.ChangeConflictException;
import org.eclipse.emf.emfstore.common.extensionpoint.ExtensionElement;
import org.eclipse.emf.emfstore.common.extensionpoint.ExtensionPoint;
import org.eclipse.emf.emfstore.common.model.ModelElementId;
import org.eclipse.emf.emfstore.common.model.Project;
import org.eclipse.emf.emfstore.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.server.conflictDetection.ConflictBucket;
import org.eclipse.emf.emfstore.server.conflictDetection.ConflictBucketCandidate;
import org.eclipse.emf.emfstore.server.conflictDetection.ConflictDetector;
import org.eclipse.emf.emfstore.server.model.versioning.ChangePackage;
import org.eclipse.emf.emfstore.server.model.versioning.LogMessage;
import org.eclipse.emf.emfstore.server.model.versioning.PrimaryVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.impl.ChangePackageImpl;
import org.eclipse.emf.emfstore.server.model.versioning.operations.AbstractOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.CompositeOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.CreateDeleteOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.MultiAttributeOperation;
import org.eclipse.emf.emfstore.server.model.versioning.operations.MultiReferenceOperation;

/**
 * DecisionManager is the controller for the merge dialog and therefore it's
 * main component. It calculates the conflicts from incoming changes and can
 * execute resolved conflicts.
 * 
 * @author wesendon
 */
public class DecisionManager {

	private final Project project;
	private final List<ChangePackage> myChangePackages;
	private final List<ChangePackage> theirChangePackages;
	private ConflictDetector conflictDetector;

	private ArrayList<Conflict> conflicts;
	private Set<AbstractOperation> notInvolvedInConflict;
	private ArrayList<AbstractOperation> acceptedMine;
	private ArrayList<AbstractOperation> rejectedTheirs;
	private final PrimaryVersionSpec baseVersion;
	private final PrimaryVersionSpec targetVersion;
	private List<ConflictHandler> conflictHandler;
	private final boolean isBranchMerge;
	private ChangeConflictException conflictException;

	/**
	 * Default constructor.
	 * 
	 * @param project
	 *            the related project
	 * @param myChangePackages
	 *            my changes
	 * @param theirChangePackages
	 *            incoming changes
	 * @param baseVersion
	 *            baseversion
	 * @param targetVersion
	 *            new target version
	 * @param isBranchMerge
	 *            allows to specify whether two branches are merged, opposed to
	 *            changes from the same branch. Has an effect on the wording of
	 *            conflictions
	 * @param conflictException a conflict exception with preliminary results
	 */
	public DecisionManager(Project project, List<ChangePackage> myChangePackages,
		List<ChangePackage> theirChangePackages, PrimaryVersionSpec baseVersion, PrimaryVersionSpec targetVersion,
		boolean isBranchMerge, ChangeConflictException conflictException) {
		this.project = project;
		this.myChangePackages = myChangePackages;
		this.theirChangePackages = theirChangePackages;
		this.baseVersion = baseVersion;
		this.targetVersion = targetVersion;
		this.isBranchMerge = isBranchMerge;
		this.conflictException = conflictException;
		this.conflictHandler = initConflictHandlers();
		this.conflictDetector = new ConflictDetector();
		init();
	}

	private List<ConflictHandler> initConflictHandlers() {
		ArrayList<ConflictHandler> result = new ArrayList<ConflictHandler>();
		for (ExtensionElement element : new ExtensionPoint("org.eclipse.emf.emfstore.client.merge.conflictHandler")
			.getExtensionElements()) {
			ConflictHandler handler = element.getClass("class", ConflictHandler.class);
			if (handler != null) {
				result.add(handler);
			}
		}
		return result;
	}

	private void init() {
		// flatten operations

		acceptedMine = new ArrayList<AbstractOperation>();
		rejectedTheirs = new ArrayList<AbstractOperation>();

		conflicts = new ArrayList<Conflict>();
		notInvolvedInConflict = new LinkedHashSet<AbstractOperation>();

		Set<ConflictBucketCandidate> conflictBucketCandidates;
		if (conflictException != null) {
			conflictBucketCandidates = conflictException.getConflictBucketCandidates();
		} else {
			conflictBucketCandidates = new ConflictDetector().calculateConflictCandidateBuckets(myChangePackages,
				theirChangePackages);
		}
		Set<ConflictBucket> conflictBucketsSet = conflictDetector.calculateConflictBucketsFromConflictCandidateBuckets(
			conflictBucketCandidates, notInvolvedInConflict);

		createConflicts(conflictBucketsSet);
	}

	/**
	 * BEGIN FACTORY TODO EXTRACT FACTORY CLASS.
	 */

	// BEGIN COMPLEX CODE
	private void createConflicts(Set<ConflictBucket> ConflictBucket) {
		// Create Conflicts from ConflictBucket
		for (ConflictBucket conf : ConflictBucket) {
			AbstractOperation my = conf.getMyOperation();
			AbstractOperation their = conf.getTheirOperation();

			// #checkRegistedHandlers adds Conflicts on its own
			if (checkRegisteredHandlers(conf)) {
				continue;

			} else if (isDiagramLayout(my) && isDiagramLayout(their)) {

				addConflict(createDiagramLayoutDecision(conf));
				continue;

			} else if (isAttribute(my) && isAttribute(their)) {

				addConflict(createAttributeAttributeDecision(conf));
				continue;

			} else if (isSingleRef(my) && isSingleRef(their)) {

				addConflict(createSingleSingleConflict(conf));
				continue;

			} else if (isMultiRef(my) && isMultiRef(their)) {

				addConflict(createMultiMultiConflict(conf));
				continue;

			} else if ((isMultiRef(my) && isSingleRef(their)) || (isMultiRef(their) && isSingleRef(my))) {

				addConflict(createMultiSingle(conf));
				continue;

			} else if (isCompositeRef(my) && isCompositeRef(their)) {

				addConflict(createReferenceConflict(conf));
				continue;

			} else if ((isCompositeRef(my) && (isMultiRef(their) || isSingleRef(their)))
				|| ((isMultiRef(my) || isSingleRef(my)) && isCompositeRef(their))) {

				addConflict(createReferenceCompVSSingleMulti(conf));
				continue;

			} else if ((isMultiRef(my) && isMultiRefSet(their)) || (isMultiRef(their) && isMultiRefSet(my))) {

				addConflict(createMultiRefMultiSet(conf));
				continue;

			} else if (isMultiRefSet(my) && isMultiRefSet(their)) {

				addConflict(createMultiRefSetSet(conf));
				continue;

			} else if ((isMultiRefSet(my) && isSingleRef(their)) || (isMultiRefSet(their) && isSingleRef(my))) {

				addConflict(createMultiSetSingle(conf));
				continue;

			} else if (isMultiAtt(my) && isMultiAtt(their)) {

				addConflict(createMultiAtt(conf));
				continue;

			} else if ((isMultiAtt(my) && isMultiAttSet(their)) || (isMultiAtt(their) && isMultiAttSet(my))) {

				addConflict(createMultiAttSet(conf));
				continue;

			} else if ((isMultiAtt(my) && isMultiAttMove(their)) || (isMultiAtt(their) && isMultiAttMove(my))) {

				addConflict(createMultiAttMove(conf));
				continue;

			} else if ((isMultiAttSet(my) && isMultiAttMove(their)) || (isMultiAttSet(their) && isMultiAttMove(my))) {

				addConflict(createMultiAttMoveSet(conf));
				continue;

			} else if (isMultiAttSet(my) && isMultiAttSet(their)) {

				addConflict(createMultiAttSetSet(conf));
				continue;

			} else if (isComposite(my) || isComposite(their)) {

				addConflict(createCompositeConflict(conf));
				continue;

			} else if (isDelete(my) || isDelete(their)) {

				addConflict(createDeleteOtherConflict(conf));

			}
		}
	}

	private boolean checkRegisteredHandlers(ConflictBucket conf) {
		for (ConflictHandler handler : this.conflictHandler) {
			if (handler.canHandle(conf)) {
				addConflict(handler.handle(this, conf));
				return true;
			}
		}
		return false;
	}

	private void addConflict(Conflict conflict) {
		if (conflict == null) {
			return;
		}
		conflicts.add(conflict);
	}

	// END COMPLEX CODE
	private Conflict createMultiRefMultiSet(ConflictBucket conf) {
		if (isMultiRef(conf.getMyOperation())) {
			return new MultiReferenceSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceSetConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiSetSingle(ConflictBucket conf) {
		if (isMultiRefSet(conf.getMyOperation())) {
			return new MultiReferenceSetSingleConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceSetSingleConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiSingle(ConflictBucket conf) {
		if (isMultiRef(conf.getMyOperation())) {
			return new MultiReferenceSingleConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceSingleConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiRefSetSet(ConflictBucket conf) {
		return new MultiReferenceSetSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
			conf.getMyOperation(), conf.getTheirOperation(), this);
	}

	private Conflict createMultiAttSetSet(ConflictBucket conf) {
		return new MultiAttributeSetSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
			conf.getMyOperation(), conf.getTheirOperation(), this);
	}

	private Conflict createMultiAtt(ConflictBucket conf) {
		if (((MultiAttributeOperation) conf.getMyOperation()).isAdd()) {
			return new MultiAttributeConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);

		}
	}

	private Conflict createMultiAttSet(ConflictBucket conf) {
		if (isMultiAtt(conf.getMyOperation())) {
			return new MultiAttributeSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeSetConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiAttMove(ConflictBucket conf) {
		if (isMultiAtt(conf.getMyOperation())) {
			return new MultiAttributeMoveConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeMoveConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createMultiAttMoveSet(ConflictBucket conf) {
		if (isMultiAttSet(conf.getMyOperation())) {
			return new MultiAttributeMoveSetConflict(conf.getMyOperations(), conf.getTheirOperations(),
				conf.getMyOperation(), conf.getTheirOperation(), this, true);
		} else {
			return new MultiAttributeMoveSetConflict(conf.getTheirOperations(), conf.getMyOperations(),
				conf.getTheirOperation(), conf.getMyOperation(), this, false);
		}
	}

	private Conflict createReferenceCompVSSingleMulti(ConflictBucket conf) {
		if (isCompositeRef(conf.getMyOperation())) {
			return createRefFromSub(conf, ((CompositeOperation) conf.getMyOperation()).getSubOperations(),
				Arrays.asList(conf.getTheirOperation()));
		} else {
			return createRefFromSub(conf, Arrays.asList(conf.getMyOperation()),
				((CompositeOperation) conf.getTheirOperation()).getSubOperations());
		}
	}

	private Conflict createReferenceConflict(ConflictBucket conf) {
		EList<AbstractOperation> myOperations = ((CompositeOperation) conf.getMyOperation()).getSubOperations();
		EList<AbstractOperation> theirOperations = ((CompositeOperation) conf.getTheirOperation()).getSubOperations();

		return createRefFromSub(conf, myOperations, theirOperations);
	}

	private Conflict createRefFromSub(ConflictBucket conf, List<AbstractOperation> myOperations,
		List<AbstractOperation> theirOperations) {

		for (AbstractOperation myOp : myOperations) {
			for (AbstractOperation theirOp : theirOperations) {
				if (conflictDetector.doConflict(myOp, theirOp)) {
					if (isSingleRef(myOp)) {

						return new ReferenceConflict(createSingleSingleConflict(myOp, theirOp), conf.getMyOperations(),
							conf.getTheirOperations(), conf.getMyOperation(), conf.getTheirOperation());

					} else if (isMultiRef(myOp)) {

						return new ReferenceConflict(createMultiMultiConflict(myOp, theirOp), conf.getMyOperations(),
							conf.getTheirOperations(), conf.getMyOperation(), conf.getTheirOperation());

					} else {
						return null;
					}
				}
			}
		}
		return null;
	}

	private Conflict createAttributeAttributeDecision(ConflictBucket conf) {
		return new AttributeConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
			conf.getTheirOperation(), this);
	}

	private Conflict createDiagramLayoutDecision(ConflictBucket conf) {
		return new DiagramLayoutConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
			conf.getTheirOperation(), this);
	}

	private Conflict createSingleSingleConflict(ConflictBucket conf) {
		return new SingleReferenceConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
			conf.getTheirOperation(), this);
	}

	private Conflict createSingleSingleConflict(AbstractOperation my, AbstractOperation their) {
		return new SingleReferenceConflict(Collections.singleton(my), Collections.singleton(their), my, their, this);
	}

	private Conflict createMultiMultiConflict(ConflictBucket conf) {
		if (((MultiReferenceOperation) conf.getMyOperation()).isAdd()) {
			return new MultiReferenceConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, true);
		} else {
			return new MultiReferenceConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, false);
		}
	}

	private Conflict createMultiMultiConflict(AbstractOperation my, AbstractOperation their) {
		if (((MultiReferenceOperation) my).isAdd()) {
			return new MultiReferenceConflict(Collections.singleton(my), Collections.singleton(their), my, their, this,
				true);
		} else {
			return new MultiReferenceConflict(Collections.singleton(their), Collections.singleton(my), their, my, this,
				false);
		}
	}

	private Conflict createDeleteOtherConflict(ConflictBucket conf) {
		if (isDelete(conf.getMyOperation())) {
			return new DeletionConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), true, this);
		} else {
			return new DeletionConflict(conf.getTheirOperations(), conf.getMyOperations(), conf.getTheirOperation(),
				conf.getMyOperation(), false, this);
		}
	}

	private Conflict createCompositeConflict(ConflictBucket conf) {
		if (isComposite(conf.getMyOperation())) {
			return new CompositeConflict(conf.getMyOperations(), conf.getTheirOperations(), conf.getMyOperation(),
				conf.getTheirOperation(), this, true);
		} else {
			return new CompositeConflict(conf.getTheirOperations(), conf.getMyOperations(), conf.getTheirOperation(),
				conf.getMyOperation(), this, false);
		}
	}

	/**
	 * FACTORY END
	 */

	/**
	 * Returns the conflicts.
	 * 
	 * @return list of conflicts.
	 */
	public ArrayList<Conflict> getConflicts() {
		return conflicts;
	}

	/**
	 * Checks whether all conflicts are resolved.
	 * 
	 * @return true if all are resolved
	 */
	public boolean isResolved() {
		boolean isResolved = true;
		for (Conflict conflict : conflicts) {
			isResolved = isResolved && conflict.isResolved();
		}
		return isResolved;
	}

	/**
	 * Get "my" accepted operations. This list will be empty, if {@link #calcResult()} hasn't been called before.
	 * 
	 * @return list of operations
	 */
	public List<AbstractOperation> getAcceptedMine() {
		return acceptedMine;
	}

	/**
	 * Get "their" accepted operations. This list will be empty, if {@link #calcResult()} hasn't been called before.
	 * 
	 * @return list of operations
	 */
	public List<AbstractOperation> getRejectedTheirs() {
		return rejectedTheirs;
	}

	/**
	 * If all conflicts are resolved this method will generate the resulting
	 * operations from the conflicts. Then call {@link #getAcceptedMine()} and {@link #getRejectedTheirs()}.
	 */
	// BEGIN COMPLEX CODE
	public void calcResult() {
		if (!isResolved()) {
			return;
		}

		Set<AbstractOperation> accceptedMineSet = new LinkedHashSet<AbstractOperation>();
		Set<AbstractOperation> rejectedTheirsSet = new LinkedHashSet<AbstractOperation>();

		for (Conflict conflict : conflicts) {
			accceptedMineSet.addAll(conflict.getAcceptedMine());
			rejectedTheirsSet.addAll(conflict.getRejectedTheirs());
		}

		// collect my accepted operations
		for (ChangePackage myChangePackage : myChangePackages) {
			for (AbstractOperation myOp : myChangePackage.getOperations()) {
				if (notInvolvedInConflict.contains(myOp)) {
					acceptedMine.add(myOp);
				} else if (accceptedMineSet.contains(myOp)) {
					acceptedMine.add(myOp);
				}
				accceptedMineSet.remove(myOp);
			}
		}

		// add all remaining operations in acceptedMineSet (they have been generated during merge)
		acceptedMine.addAll(accceptedMineSet);

		for (ChangePackage theirCP : theirChangePackages) {
			for (AbstractOperation theirOp : theirCP.getOperations()) {
				if (rejectedTheirsSet.contains(theirOp)) {
					rejectedTheirs.add(theirOp);
				}
			}
		}
	}

	// END COMPLEX CODE

	/**
	 * Returns the conflictdetector.
	 * 
	 * @return conflictdetector
	 */
	public ConflictDetector getConflictDetector() {
		return conflictDetector;
	}

	/**
	 * Flat whether branches are merged opposed to versions on the same branch.
	 * 
	 * @return true, if branches
	 */
	public boolean isBranchMerge() {
		return isBranchMerge;
	}

	/**
	 * Get the Name of an model element by modelelement id.
	 * 
	 * @param modelElementId
	 *            id of element
	 * @return name as string
	 */
	public String getModelElementName(ModelElementId modelElementId) {
		return getModelElementName(getModelElement(modelElementId));
	}

	/**
	 * Get the Name of an model element.
	 * 
	 * @param modelElement
	 *            element
	 * @return name as string
	 */
	public String getModelElementName(EObject modelElement) {
		return DecisionUtil.getModelElementName(modelElement);
	}

	/**
	 * Returns the modelelement. Therefore the project as well as creation and
	 * deletion operations are searched.
	 * 
	 * @param modelElementId
	 *            id of element.
	 * @return modelelement
	 */
	// TODO: SLOW!
	public EObject getModelElement(ModelElementId modelElementId) {
		EObject modelElement = project.getModelElement(modelElementId);
		if (modelElement == null) {
			for (ChangePackage cp : myChangePackages) {
				modelElement = searchForCreatedME(modelElementId, cp.getOperations());
				if (modelElement != null) {
					break;
				}
			}
			if (modelElement == null) {
				for (ChangePackage cp : theirChangePackages) {
					modelElement = searchForCreatedME(modelElementId, cp.getOperations());
					if (modelElement != null) {
						break;
					}
				}
			}
		}
		return modelElement;
	}

	private EObject searchForCreatedME(ModelElementId modelElementId, List<AbstractOperation> operations) {
		for (AbstractOperation operation : operations) {
			EObject result = null;
			if (operation instanceof CreateDeleteOperation) {
				result = searchCreateAndDelete((CreateDeleteOperation) operation, modelElementId);

			} else if (operation instanceof CompositeOperation) {
				EList<AbstractOperation> subOperations = ((CompositeOperation) operation).getSubOperations();
				result = searchForCreatedME(modelElementId, subOperations);
			} else {
				continue;
			}
			if (result != null) {
				return result;
			}
		}
		return null;
	}

	private EObject searchCreateAndDelete(CreateDeleteOperation cdo, ModelElementId modelElementId) {
		EObject modelElement = cdo.getModelElement();
		if (modelElement == null) {
			return null;
		}
		Set<EObject> containedModelElements = ModelUtil.getAllContainedModelElements(modelElement, false);
		containedModelElements.add(modelElement);

		for (EObject child : containedModelElements) {
			ModelElementId childId = ModelUtil.clone(cdo.getEObjectToIdMap().get(child));
			if (childId != null && childId.equals(modelElementId)) {
				return child;
			}
		}
		return null;
	}

	/**
	 * Returns the name of the author for a operation in list of their
	 * operations.
	 * 
	 * @param theirOperation
	 *            operation
	 * @return name as string or ""
	 */
	public String getAuthorForOperation(AbstractOperation theirOperation) {
		for (ChangePackage cp : theirChangePackages) {
			for (AbstractOperation op : cp.getOperations()) {
				List<AbstractOperation> tmpList = new ArrayList<AbstractOperation>();
				if (op instanceof CompositeOperation) {
					tmpList.add(op);
					tmpList.addAll(((CompositeOperation) op).getSubOperations());
				} else {
					tmpList.add(op);
				}
				for (AbstractOperation ao : tmpList) {
					if (ao.equals(theirOperation)) {
						LogMessage log = cp.getLogMessage();
						if (log == null) {
							return "";
						}
						return (log.getAuthor() == null) ? "" : log.getAuthor();

					}
				}
			}
		}
		return "";
	}

	/**
	 * Return the related project.
	 * 
	 * @return project
	 */
	public Project getProject() {
		return project;
	}

	/**
	 * Returns the baseVersion of the project which is updating.
	 * 
	 * @return version
	 */
	public PrimaryVersionSpec getBaseVersion() {
		return baseVersion;
	}

	/**
	 * Returns the targetVersion of the update which caused merging.
	 * 
	 * @return version
	 */
	public PrimaryVersionSpec getTargetVersion() {
		return targetVersion;
	}

	/**
	 * Gives Access to internal values. Use with care.
	 */
	// BEGIN COMPLEX CODE
	public DecisionManager.Internal Internal = this.new Internal();

	// END COMPLEX CODE

	/**
	 * This class allows access to internal values. Use with care.
	 * 
	 * @author wesendon
	 */
	public class Internal {
		/**
		 * My CP.
		 * 
		 * @return list of cp
		 */
		public List<ChangePackage> getMyChangePackages() {
			return myChangePackages;
		}

		/**
		 * Their CP.
		 * 
		 * @return list of cp
		 */
		public List<ChangePackage> getTheirChangePackages() {
			return theirChangePackages;
		}
	}

	private Integer myLeafOperationCount;

	/**
	 * Count my leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countMyLeafOperations() {
		if (myLeafOperationCount == null) {
			countConflicts();
		}
		return myLeafOperationCount;
	}

	private void countConflicts() {
		int myCount = 0;
		int myLeafCount = 0;
		int theirCount = 0;
		int theirLeafCount = 0;
		for (Conflict conflict : conflicts) {
			myCount += conflict.getLeftOperations().size();
			myLeafCount += ChangePackageImpl.countLeafOperations(conflict.getMyOperations());
			theirCount += conflict.getRightOperations().size();
			theirLeafCount += ChangePackageImpl.countLeafOperations(conflict.getTheirOperations());
		}
		myOperationCount = myCount;
		myLeafOperationCount = myLeafCount;
		theirOperationCount = theirCount;
		theirLeafOperationCount = theirLeafCount;

	}

	private Integer theirLeafOperationCount;

	/**
	 * Count their leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countTheirLeafOperations() {
		if (theirLeafOperationCount == null) {
			countConflicts();
		}
		return theirLeafOperationCount;
	}

	private Integer myOperationCount;

	/**
	 * Count my leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countMyOperations() {
		if (myOperationCount == null) {
			countConflicts();
		}
		return myOperationCount;
	}

	private Integer theirOperationCount;

	/**
	 * Count their leaf operations.
	 * 
	 * @return the number of leaf operations
	 */
	public int countTheirOperations() {
		if (theirOperationCount == null) {
			countConflicts();
		}
		return theirOperationCount;
	}

}

{'fraction_non_alphanumeric': 0.10891106082424741, 'fraction_numerical': 0.00048001097167935265, 'mean_word_length': 4.217154100706556, 'pattern_counts': {'":': 0, '<': 77, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5331538', 'n_tokens_mistral': 19018, 'n_tokens_neox': 16318, 'n_words': 3291}