/*******************************************************************************
* 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.server.conflictDetection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.emfstore.server.model.versioning.ChangePackage;
import org.eclipse.emf.emfstore.server.model.versioning.operations.AbstractOperation;
/**
* Detects conflicts with a given {@link ConflictDetectionStrategy}.
*
* @author koegel
*/
public class ConflictDetector {
private ConflictDetectionStrategy conflictDetectionStrategy;
/**
* Constructor. Uses default conflict detection strategy
*/
public ConflictDetector() {
// this(new AlwaysFalseConflictDetectionStrategy());
// this(new FineGrainedConflictDetectionStrategy());
this(new IndexSensitiveConflictDetectionStrategy());
}
/**
* Constructor with a given strategy.
*
* @param conflictDetectionStrategy the detection strategy to use
*/
public ConflictDetector(ConflictDetectionStrategy conflictDetectionStrategy) {
this.conflictDetectionStrategy = conflictDetectionStrategy;
}
/**
* Determines if two change packages are conflicting.
*
* @param changePackageA a changePackage
* @param changePackageB another change package
* @return true if the two packages conflict
*/
public boolean doConflict(ChangePackage changePackageA, ChangePackage changePackageB) {
for (AbstractOperation operation : changePackageA.getOperations()) {
for (AbstractOperation otherOperation : changePackageB.getOperations()) {
if (doConflict(operation, otherOperation)) {
return true;
}
}
}
return false;
}
/**
* Determines if two changepackages are conflicting.
*
* @param operation operation
* @param otherOperation otheroperation
* @return true, if conflicting
*/
public boolean doConflict(AbstractOperation operation, AbstractOperation otherOperation) {
return conflictDetectionStrategy.doConflict(operation, otherOperation);
}
/**
* Determine if a change package conflicts with a list of change packages.
*
* @param changePackage a change package
* @param changePackageList a list of change package
* @return true if the change package conflicts with any package in the list
*/
public boolean doConflict(ChangePackage changePackage, List<ChangePackage> changePackageList) {
for (ChangePackage b : changePackageList) {
if (doConflict(changePackage, b)) {
return true;
}
}
return false;
}
/**
* Retrieve all operations in other ops that are conflicting with operations in ops. If any operation is in both
* lists, it is not considered to be conflicting.
*
* @param ops A list of operations.
* @param otherOps A list of the other operations.
* @return A set of conflicting operations which is a subset of otherOps.
*/
public Set<AbstractOperation> getConflicting(List<AbstractOperation> ops, List<AbstractOperation> otherOps) {
// the operations that are conflicting
Set<AbstractOperation> conflicting = new HashSet<AbstractOperation>();
// check each operation in ops against otherOps
for (AbstractOperation position : ops) {
for (AbstractOperation other : otherOps) {
if (conflicting.contains(other)) {
// a conflict has already been registered
continue;
}
// if there is a conflict, add the other op to the
// list of conflicting ops along with all ops that
// require other ops
if (conflictDetectionStrategy.doConflict(position, other)) {
conflicting.addAll(getRequiring(otherOps, other));
conflicting.add(other);
}
}
}
// return the set of conflicting operations in other ops
return conflicting;
}
/**
* Retrieve all operations in other ops that are conflicting on "index integrity only" with operations in ops. If
* any operation is in both lists, it is not considered to be conflicting.
*
* @param ops A list of operations.
* @param otherOps A list of the other operations.
* @return A set of conflicting operations which is a subset of otherOps.
*/
public Set<AbstractOperation> getConflictingIndexIntegrity(List<AbstractOperation> ops,
List<AbstractOperation> otherOps) {
// the operations that are conflicting
Set<AbstractOperation> conflicting = new HashSet<AbstractOperation>();
// works with only one strategy, as of now, hardcoding it
IndexSensitiveConflictDetectionStrategy indexSensitiveStrategy = new IndexSensitiveConflictDetectionStrategy();
// check each operation in ops against otherOps
for (AbstractOperation position : ops) {
for (AbstractOperation other : otherOps) {
if (conflicting.contains(other)) {
// a conflict has already been registered
continue;
}
// if there is a conflict, add the other op to the
// list of conflicting ops along with all ops that
// require other ops
if (indexSensitiveStrategy.doConflictIndexIntegrity(position, other)) {
conflicting.addAll(getRequiring(otherOps, other));
conflicting.add(other);
}
}
}
// return the set of conflicting operations in other ops
return conflicting;
}
/**
* Retrieve all operations in ops that are required by op. The operation <code>op</code> must be part of
* <code>ops</code>.
*
* @param ops A time ordered (ascending) list of operations containing op.
* @param op The operation for which the requirements should be determined.
* @return A list of operations that are required for op which is a subset of ops. <code>op</code> will not be part
* of the returned list.
* @throws IllegalArgumentException If op is not in ops.
*/
public List<AbstractOperation> getRequired(List<AbstractOperation> ops, AbstractOperation op)
throws IllegalArgumentException {
// sanity check
if (!ops.contains(op)) {
throw new IllegalArgumentException("the ops list dos not contain op");
}
// Check all operations if they are (transitively) required by op.
// We make use of the fact here that an operation can only require
// operations that are before it in time.
List<AbstractOperation> required = new ArrayList<AbstractOperation>();
required.add(op);
for (int i = ops.indexOf(op) - 1; i >= 0; i--) {
AbstractOperation current = ops.get(i);
// else check if it is required by any of the already required ops
for (AbstractOperation req : required) {
if (conflictDetectionStrategy.isRequired(current, req)) {
required.add(0, current);
break;
}
}
}
required.remove(op);
// return the sub list of operations required by op
return required;
}
/**
* Retrieve all operations in ops that require op.
*
* @param ops A time ordered (ascending) list of operations containing op.
* @param op The operation for which the dependants should be determined.
* @return A list of operations that require op which is a subset of ops.
*/
public List<AbstractOperation> getRequiring(List<AbstractOperation> ops, AbstractOperation op) {
// sanity check
if (!ops.contains(op)) {
throw new IllegalArgumentException("the ops list dos not contain op");
}
// Check all operations if they (transitively) require op.
// We make use of the fact here that an operation can only
// possibly require an operation that is before it in time.
int opIdx = ops.indexOf(op);
List<AbstractOperation> requiring = new ArrayList<AbstractOperation>();
requiring.add(op);
for (AbstractOperation current : ops) {
// if the current op is before op, it can not require it
if (ops.indexOf(current) <= opIdx) {
continue;
}
// else check if it requires any of the already requiring ops
for (AbstractOperation req : requiring) {
if (conflictDetectionStrategy.isRequired(req, current)) {
requiring.add(current);
break;
}
}
}
requiring.remove(op);
// return the sub list of operations requiring op
return requiring;
}
/**
* Return all operations that are involved in a conflict of the two lists.
*
* @param operationListA a list of operations
* @param operationListB another list of operations
* @return a set of operations
*/
public Set<AbstractOperation> getAllConflictInvolvedOperations(List<AbstractOperation> operationListA,
List<AbstractOperation> operationListB) {
Set<AbstractOperation> result = new HashSet<AbstractOperation>();
for (AbstractOperation operationA : operationListA) {
if (result.contains(operationA)) {
continue;
}
List<AbstractOperation> reqAoperations = getRequiring(operationListA, operationA);
Set<AbstractOperation> conflicting = getConflicting(reqAoperations, operationListB);
if (conflicting.size() > 0) {
result.addAll(conflicting);
result.add(operationA);
}
}
for (AbstractOperation operationB : operationListB) {
if (result.contains(operationB)) {
continue;
}
List<AbstractOperation> reqAoperations = getRequiring(operationListB, operationB);
Set<AbstractOperation> conflicting = getConflicting(reqAoperations, operationListA);
if (conflicting.size() > 0) {
result.addAll(conflicting);
result.add(operationB);
}
}
return result;
}
/**
* Filter the change packages that only operations involved in a conflict remain. Empty ChangePackages are removed
* from the list.
*
* @param myChanges my local change package
* @param theirChanges the change packages from the repository
*/
public void filterToConflictInvolved(ChangePackage myChanges, List<ChangePackage> theirChanges) {
List<AbstractOperation> theirOperations = new ArrayList<AbstractOperation>();
for (ChangePackage theirChangePackage : theirChanges) {
for (AbstractOperation theirOperation : theirChangePackage.getOperations()) {
theirOperations.add(theirOperation);
}
}
EList<AbstractOperation> myOperations = myChanges.getOperations();
Set<AbstractOperation> allConflictInvolvedOperations = getAllConflictInvolvedOperations(myOperations,
theirOperations);
// filter my change package
Set<AbstractOperation> myOperationsToRemove = new HashSet<AbstractOperation>();
for (AbstractOperation myOperation : myOperations) {
if (!allConflictInvolvedOperations.contains(myOperation)) {
myOperationsToRemove.add(myOperation);
}
}
myOperations.removeAll(myOperationsToRemove);
// filter their change package
List<ChangePackage> changePackagesToRemove = new ArrayList<ChangePackage>();
for (ChangePackage theirChangePackage : theirChanges) {
Set<AbstractOperation> theirOperationsToRemove = new HashSet<AbstractOperation>();
EList<AbstractOperation> theirOperations2 = theirChangePackage.getOperations();
for (AbstractOperation theirOperation : theirOperations2) {
if (!allConflictInvolvedOperations.contains(theirOperation)) {
theirOperationsToRemove.add(theirOperation);
}
}
theirOperations2.removeAll(theirOperationsToRemove);
if (theirOperations2.size() == 0) {
changePackagesToRemove.add(theirChangePackage);
}
}
theirChanges.removeAll(changePackagesToRemove);
}
}