All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.activej.ot.system.OTSystemImpl Maven / Gradle / Ivy

Go to download

Implementation of operational transformation technology. Allows building collaborative software systems.

There is a newer version: 6.0-rc2
Show newest version
/*
 * 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 transform(L left, R right) throws TransformException;
	}

	@FunctionalInterface
	public interface SquashFunction {
		@Nullable OP trySquash(OP1 op1, OP2 op2);
	}

	@FunctionalInterface
	public interface InvertFunction {
		List invert(OP2 op);
	}

	@FunctionalInterface
	public interface EmptyPredicate {
		boolean isEmpty(OP op);
	}

	private static class KeyPair {
		public final Class left;
		public final Class right;

		public KeyPair(Class left, Class 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> emptyPredicates = new HashMap<>();

	private OTSystemImpl() {
	}

	public static  OTSystemImpl create() {
		return new OTSystemImpl<>();
	}

	@SuppressWarnings({"unchecked", "rawtypes"})
	public  OTSystemImpl withTransformFunction(Class leftType, Class rightType,
			TransformFunction transformer) {
		transformers.put(new KeyPair(leftType, rightType), transformer);
		if (leftType != rightType) {
			transformers.put(new KeyPair(rightType, leftType), (TransformFunction) (left, right) -> {
				TransformResult 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 opType1, Class opType2,
			SquashFunction squashFunction) {
		squashers.put(new KeyPair(opType1, opType2), squashFunction);
		return this;
	}

	@SuppressWarnings("unchecked")
	public  OTSystemImpl withInvertFunction(Class opType, InvertFunction inverter) {
		inverters.put((Class) opType, inverter);
		return this;
	}

	@SuppressWarnings("unchecked")
	public  OTSystemImpl withEmptyPredicate(Class 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 leftDiffs, List rightDiffs) throws TransformException {
		TransformResult transform = doTransform(leftDiffs, rightDiffs);
		if (!transform.hasConflict())
			return transform;
		return resolveConflicts(leftDiffs, rightDiffs, transform);
	}

	private TransformResult resolveConflicts(List leftDiffs, List 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 leftDiffs, List 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 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 inverted = inverter.invert(op);
			result.addAll(inverted);
		}
		return result;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy