io.activej.ot.system.OTSystemImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activej-ot Show documentation
Show all versions of activej-ot Show documentation
Implementation of operational transformation technology. Allows building collaborative software systems.
/*
* Copyright (C) 2020 ActiveJ LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.activej.ot.system;
import io.activej.ot.TransformResult;
import io.activej.ot.TransformResult.ConflictResolution;
import io.activej.ot.exception.TransformException;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static io.activej.common.Utils.concat;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.concat;
public final class OTSystemImpl implements OTSystem {
@FunctionalInterface
public interface TransformFunction {
TransformResult extends OP> transform(L left, R right) throws TransformException;
}
@FunctionalInterface
public interface SquashFunction {
@Nullable OP trySquash(OP1 op1, OP2 op2);
}
@FunctionalInterface
public interface InvertFunction {
List extends OP> invert(OP2 op);
}
@FunctionalInterface
public interface EmptyPredicate {
boolean isEmpty(OP op);
}
private static class KeyPair {
public final Class extends O> left;
public final Class extends O> right;
public KeyPair(Class extends O> left, Class extends O> right) {
this.left = left;
this.right = right;
}
@SuppressWarnings({"EqualsWhichDoesntCheckParameterClass", "RedundantIfStatement"})
@Override
public boolean equals(Object o) {
if (this == o) return true;
KeyPair> key = (KeyPair>) o;
if (!left.equals(key.left)) return false;
if (!right.equals(key.right)) return false;
return true;
}
@Override
public int hashCode() {
int result = left.hashCode();
result = 31 * result + right.hashCode();
return result;
}
}
private final Map, TransformFunction> transformers = new HashMap<>();
private final Map, SquashFunction> squashers = new HashMap<>();
private final Map, InvertFunction> inverters = new HashMap<>();
private final Map, EmptyPredicate extends D>> emptyPredicates = new HashMap<>();
private OTSystemImpl() {
}
public static OTSystemImpl create() {
return new OTSystemImpl<>();
}
@SuppressWarnings({"unchecked", "rawtypes"})
public OTSystemImpl withTransformFunction(Class super L> leftType, Class super R> rightType,
TransformFunction transformer) {
transformers.put(new KeyPair(leftType, rightType), transformer);
if (leftType != rightType) {
transformers.put(new KeyPair(rightType, leftType), (TransformFunction) (left, right) -> {
TransformResult extends D> transform = transformer.transform(right, left);
if (transform.hasConflict()) {
if (transform.resolution == ConflictResolution.LEFT)
return TransformResult.conflict(ConflictResolution.RIGHT);
if (transform.resolution == ConflictResolution.RIGHT)
return TransformResult.conflict(ConflictResolution.LEFT);
return transform;
}
return TransformResult.of(transform.right, transform.left);
});
}
return this;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public OTSystemImpl withSquashFunction(Class super O1> opType1, Class super O2> opType2,
SquashFunction squashFunction) {
squashers.put(new KeyPair(opType1, opType2), squashFunction);
return this;
}
@SuppressWarnings("unchecked")
public OTSystemImpl withInvertFunction(Class super O> opType, InvertFunction inverter) {
inverters.put((Class) opType, inverter);
return this;
}
@SuppressWarnings("unchecked")
public OTSystemImpl withEmptyPredicate(Class super O> opType, EmptyPredicate emptyChecker) {
emptyPredicates.put((Class) opType, emptyChecker);
return this;
}
@SuppressWarnings({"SimplifiableIfStatement", "unchecked"})
@Override
public boolean isEmpty(D op) {
if (emptyPredicates.isEmpty())
return false;
EmptyPredicate emptyChecker = (EmptyPredicate) emptyPredicates.get(op.getClass());
if (emptyChecker == null)
return false;
return emptyChecker.isEmpty(op);
}
@Override
public TransformResult transform(List extends D> leftDiffs, List extends D> rightDiffs) throws TransformException {
TransformResult transform = doTransform(leftDiffs, rightDiffs);
if (!transform.hasConflict())
return transform;
return resolveConflicts(leftDiffs, rightDiffs, transform);
}
private TransformResult resolveConflicts(List extends D> leftDiffs, List extends D> rightDiffs, TransformResult transform) {
if (transform.resolution == ConflictResolution.LEFT) {
return TransformResult.of(transform.resolution,
emptyList(),
squash(concat(invert(rightDiffs).stream(), leftDiffs.stream()).collect(toList())));
}
if (transform.resolution == ConflictResolution.RIGHT) {
return TransformResult.of(transform.resolution,
squash(concat(invert(leftDiffs).stream(), rightDiffs.stream()).collect(toList())),
emptyList());
}
throw new AssertionError();
}
@SuppressWarnings({"unchecked", "rawtypes"})
public TransformResult doTransform(List extends D> leftDiffs, List extends D> rightDiffs) throws TransformException {
if (leftDiffs.isEmpty() && rightDiffs.isEmpty())
return TransformResult.empty();
if (leftDiffs.isEmpty()) {
return TransformResult.left(rightDiffs);
}
if (rightDiffs.isEmpty()) {
return TransformResult.right(leftDiffs);
}
if (leftDiffs.size() == 1) {
D left = leftDiffs.get(0);
D right = rightDiffs.get(0);
KeyPair key = new KeyPair(left.getClass(), right.getClass());
TransformFunction transformer = (TransformFunction) transformers.get(key);
TransformResult transform1 = (TransformResult) transformer.transform(left, right);
if (transform1.hasConflict()) return transform1;
TransformResult transform2 = doTransform(transform1.right, rightDiffs.subList(1, rightDiffs.size()));
if (transform2.hasConflict()) return transform2;
return TransformResult.of(concat(transform1.left, transform2.left), transform2.right);
}
TransformResult transform1 = doTransform(leftDiffs.subList(0, 1), rightDiffs);
if (transform1.hasConflict()) return transform1;
TransformResult transform2 = doTransform(leftDiffs.subList(1, leftDiffs.size()), transform1.left);
if (transform2.hasConflict()) return transform2;
return TransformResult.of(transform2.left, concat(transform1.right, transform2.right));
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public List squash(List extends D> ops) {
if (squashers.isEmpty())
return (List) ops;
List result = new ArrayList<>();
Iterator it = ((List) ops).iterator();
if (!it.hasNext())
return emptyList();
D cur = it.next();
while (it.hasNext()) {
D next = it.next();
KeyPair key = new KeyPair(cur.getClass(), next.getClass());
SquashFunction squashFunction = (SquashFunction) squashers.get(key);
D squashed = squashFunction == null ? null : squashFunction.trySquash(cur, next);
if (squashed != null) {
cur = squashed;
} else {
if (!isEmpty(cur)) {
result.add(cur);
}
cur = next;
}
}
if (!isEmpty(cur)) {
result.add(cur);
}
return result;
}
@SuppressWarnings("unchecked")
@Override
public List invert(List ops) {
int size = ops.size();
List result = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
O op = ops.get(size - i - 1);
InvertFunction inverter = (InvertFunction) inverters.get(op.getClass());
List extends D> inverted = inverter.invert(op);
result.addAll(inverted);
}
return result;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy