ConflictDetector.java
package com.oliwier.listmebackend.crdt;
import com.oliwier.listmebackend.domain.model.CrdtOperation;
import java.util.ArrayList;
import java.util.List;
/**
* Detects conflicting (concurrent) operations in a list of CRDT operations.
*
* Two operations on the same target (same itemId) are concurrent if
* neither of their vector clocks is causally before the other.
*/
public class ConflictDetector {
public record Conflict(CrdtOperation a, CrdtOperation b) {}
/**
* Returns all pairs of operations that are CONCURRENT on the same item.
* Caller is responsible for applying merge strategy to resolve them.
*/
public static List<Conflict> detect(List<CrdtOperation> ops) {
List<Conflict> conflicts = new ArrayList<>();
for (int i = 0; i < ops.size(); i++) {
for (int j = i + 1; j < ops.size(); j++) {
CrdtOperation a = ops.get(i);
CrdtOperation b = ops.get(j);
// Only compare ops that target the same item
if (!sameTarget(a, b)) continue;
VectorClock clockA = VectorClock.of(a.getVectorClock());
VectorClock clockB = VectorClock.of(b.getVectorClock());
if (clockA.compare(clockB) == VectorClock.Relation.CONCURRENT) {
conflicts.add(new Conflict(a, b));
}
}
}
return conflicts;
}
/** Extract itemId from payload for target comparison. */
private static boolean sameTarget(CrdtOperation a, CrdtOperation b) {
Object targetA = a.getPayload().get("itemId");
Object targetB = b.getPayload().get("itemId");
if (targetA == null || targetB == null) return false;
return targetA.equals(targetB);
}
}