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

io.datakernel.crdt.primitives.LWWSet Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright (C) 2015-2018 SoftIndex 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.datakernel.crdt.primitives;

import io.datakernel.common.time.CurrentTimeProvider;
import io.datakernel.serializer.BinarySerializer;
import io.datakernel.serializer.util.BinaryInput;
import io.datakernel.serializer.util.BinaryOutput;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.Math.max;
import static java.util.stream.Collectors.toMap;

public final class LWWSet implements Set, CrdtType> {
	private final Map set;

	CurrentTimeProvider now = CurrentTimeProvider.ofSystem();

	private LWWSet(Map set) {
		this.set = set;
	}

	public LWWSet() {
		this(new HashMap<>());
	}

	@SafeVarargs
	public static  LWWSet of(T... items) {
		LWWSet set = new LWWSet<>();
		set.addAll(Arrays.asList(items));
		return set;
	}

	@Override
	public LWWSet merge(LWWSet other) {
		Map newSet = new HashMap<>(set);
		for (Entry entry : other.set.entrySet()) {
			newSet.merge(entry.getKey(), entry.getValue(), (ts1, ts2) -> new Timestamps(max(ts1.added, ts2.added), max(ts1.removed, ts2.removed)));
		}
		return new LWWSet<>(newSet);
	}

	@Override
	@Nullable
	public LWWSet extract(long timestamp) {
		Map newSet = set.entrySet().stream()
				.filter(entry -> entry.getValue().added > timestamp || entry.getValue().removed > timestamp)
				.collect(toMap(Entry::getKey, Entry::getValue));
		if (newSet.isEmpty()) {
			return null;
		}
		return new LWWSet<>(newSet);
	}

	@Override
	public Stream stream() {
		return set.entrySet().stream().filter(e -> e.getValue().exists()).map(Entry::getKey);
	}

	@Override
	public int size() {
		//noinspection ReplaceInefficientStreamCount ikwiad
		return (int) stream().count();
	}

	@Override
	public boolean isEmpty() {
		return size() == 0;
	}

	@Override
	public boolean contains(Object o) {
		//noinspection SuspiciousMethodCalls
		Timestamps timestamps = set.get(o);
		return timestamps != null && timestamps.added >= timestamps.removed;
	}

	@Override
	public Iterator iterator() {
		return stream().iterator();
	}

	@Override
	public Object[] toArray() {
		//noinspection SimplifyStreamApiCallChains
		return stream().toArray();
	}

	@SuppressWarnings("SuspiciousToArrayCall")
	@Override
	public  T[] toArray(@NotNull T[] a) {
		return stream().toArray($ -> a);
	}

	@Override
	public boolean add(E t) {
		Timestamps timestamps = set.get(t);
		if (timestamps == null) {
			set.put(t, new Timestamps(now.currentTimeMillis(), 0));
			return true;
		}
		boolean notExisted = !timestamps.exists();
		timestamps.added = now.currentTimeMillis();
		return notExisted;
	}

	@Override
	@SuppressWarnings("unchecked")
	public boolean remove(Object o) {
		//noinspection SuspiciousMethodCalls
		Timestamps timestamps = set.get(o);
		if (timestamps == null) {
			set.put((E) o, new Timestamps(0, now.currentTimeMillis()));
			return false;
		}
		boolean existed = timestamps.exists();
		timestamps.removed = now.currentTimeMillis();
		return existed;
	}

	@Override
	public boolean containsAll(Collection c) {
		return stream().allMatch(c::contains);
	}

	@Override
	public boolean addAll(Collection c) {
		boolean added = false;
		for (E e : c) {
			added |= add(e);
		}
		return added;
	}

	@Override
	public boolean retainAll(@NotNull Collection c) {
		boolean removed = false;
		for (E item : this) {
			if (!c.contains(item)) {
				remove(item);
				removed = true;
			}
		}
		return removed;
	}

	@Override
	public boolean removeAll(Collection c) {
		boolean removed = false;
		for (Object o : c) {
			removed |= remove(o);
		}
		return removed;
	}

	@Override
	public void clear() {
		forEach(this::remove);
	}

	@Override
	public String toString() {
		return set.entrySet()
				.stream()
				.filter(e -> e.getValue().exists())
				.map(e -> Objects.toString(e.getKey()))
				.collect(Collectors.joining(", ", "[", "]"));
	}

	private static final class Timestamps {
		long added, removed;

		Timestamps(long added, long removed) {
			this.added = added;
			this.removed = removed;
		}

		boolean exists() {
			return added >= removed;
		}
	}

	public static class Serializer implements BinarySerializer> {
		private final BinarySerializer valueSerializer;

		public Serializer(BinarySerializer valueSerializer) {
			this.valueSerializer = valueSerializer;
		}

		@Override
		public void encode(BinaryOutput out, LWWSet item) {
			out.writeVarInt(item.set.size());
			for (Entry entry : item.set.entrySet()) {
				valueSerializer.encode(out, entry.getKey());
				Timestamps timestamps = entry.getValue();
				out.writeLong(timestamps.added);
				out.writeLong(timestamps.removed);
			}
		}

		@Override
		public LWWSet decode(BinaryInput in) {
			int size = in.readVarInt();
			Map set = new HashMap<>(size);
			for (int i = 0; i < size; i++) {
				set.put(valueSerializer.decode(in), new Timestamps(in.readLong(), in.readLong()));
			}
			return new LWWSet<>(set);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy