org.jsimpledb.kv.mvcc.Writes Maven / Gradle / Ivy
Show all versions of jsimpledb-kv Show documentation
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package org.jsimpledb.kv.mvcc;
import com.google.common.base.Converter;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.TreeMap;
import org.jsimpledb.kv.KVStore;
import org.jsimpledb.kv.KeyRange;
import org.jsimpledb.kv.KeyRanges;
import org.jsimpledb.kv.util.KeyListEncoder;
import org.jsimpledb.util.ByteUtil;
import org.jsimpledb.util.ConvertedNavigableMap;
import org.jsimpledb.util.ImmutableNavigableMap;
import org.jsimpledb.util.LongEncoder;
import org.jsimpledb.util.UnsignedIntEncoder;
/**
* Holds a set of writes to a {@link KVStore}.
*
*
* Each mutation is either a key/value put, the removal of a key range (possibly containing only a single key),
* or a counter adjustment.
*
*
* Instances are not thread safe.
*/
public class Writes implements Cloneable, Mutations {
private /*final*/ KeyRanges removes;
private /*final*/ NavigableMap puts;
private /*final*/ NavigableMap adjusts;
private /*final*/ boolean immutable;
public Writes() {
this(KeyRanges.empty(), new TreeMap<>(ByteUtil.COMPARATOR), new TreeMap<>(ByteUtil.COMPARATOR), false);
}
private Writes(KeyRanges removes,
NavigableMap puts, NavigableMap adjusts, boolean immutable) {
this.removes = removes;
this.puts = puts;
this.adjusts = adjusts;
this.immutable = immutable;
}
// Accessors
/**
* Get the key ranges removals contained by this instance.
*
* @return key ranges removed
*/
public KeyRanges getRemoves() {
return this.removes;
}
/**
* Get the written key/value pairs contained by this instance.
*
*
* The caller must not modify any of the returned {@code byte[]} arrays.
*
* @return mapping from key to corresponding value
*/
public NavigableMap getPuts() {
return this.puts;
}
/**
* Get the set of counter adjustments contained by this instance.
*
*
* The caller must not modify any of the returned {@code byte[]} arrays.
*
* @return mapping from key to corresponding counter adjustment
*/
public NavigableMap getAdjusts() {
return this.adjusts;
}
/**
* Determine whether this instance is empty, i.e., contains zero mutations.
*
* @return true if this instance contains zero mutations, otherwise false
*/
public boolean isEmpty() {
return this.removes.isEmpty() && this.puts.isEmpty() && this.adjusts.isEmpty();
}
/**
* Clear all mutations.
*/
public void clear() {
this.removes.clear();
this.puts.clear();
this.adjusts.clear();
}
// Mutations
@Override
public NavigableSet getRemoveRanges() {
return this.removes.asSet();
}
@Override
public Iterable> getPutPairs() {
return this.getPuts().entrySet();
}
@Override
public Iterable> getAdjustPairs() {
return this.getAdjusts().entrySet();
}
// Application
/**
* Apply all mutations contained in this instance to the given {@link KVStore}.
*
*
* Mutations are applied in this order: removes, puts, counter adjustments.
*
* @param target target for recorded mutations
* @throws IllegalArgumentException if {@code target} is null
*/
public void applyTo(KVStore target) {
Writes.apply(this, target);
}
/**
* Apply all the given {@link Mutations} to the given {@link KVStore}.
*
*
* Mutations are applied in this order: removes, puts, counter adjustments.
*
* @param mutations mutations to apply
* @param target target for mutations
* @throws IllegalArgumentException if either parameter is null
* @throws UnsupportedOperationException if this instance is immutable
*/
public static void apply(Mutations mutations, KVStore target) {
Preconditions.checkArgument(mutations != null, "null mutations");
Preconditions.checkArgument(target != null, "null target");
for (KeyRange remove : mutations.getRemoveRanges())
target.removeRange(remove.getMin(), remove.getMax());
for (Map.Entry entry : mutations.getPutPairs())
target.put(entry.getKey(), entry.getValue());
for (Map.Entry entry : mutations.getAdjustPairs())
target.adjustCounter(entry.getKey(), entry.getValue());
}
// Serialization
/**
* Serialize this instance.
*
* @param out output
* @throws IOException if an error occurs
*/
public void serialize(OutputStream out) throws IOException {
// Removes
this.removes.serialize(out);
// Puts
UnsignedIntEncoder.write(out, this.puts.size());
byte[] prev = null;
for (Map.Entry entry : this.puts.entrySet()) {
final byte[] key = entry.getKey();
final byte[] value = entry.getValue();
KeyListEncoder.write(out, key, prev);
KeyListEncoder.write(out, value, null);
prev = key;
}
// Adjusts
UnsignedIntEncoder.write(out, this.adjusts.size());
prev = null;
for (Map.Entry entry : this.adjusts.entrySet()) {
final byte[] key = entry.getKey();
final long value = entry.getValue();
KeyListEncoder.write(out, key, prev);
LongEncoder.write(out, value);
prev = key;
}
}
/**
* Calculate the number of bytes required to serialize this instance via {@link #serialize serialize()}.
*
* @return number of serialized bytes
*/
public long serializedLength() {
// Removes
long total = this.removes.serializedLength();
// Puts
total += UnsignedIntEncoder.encodeLength(this.puts.size());
byte[] prev = null;
for (Map.Entry entry : this.puts.entrySet()) {
final byte[] key = entry.getKey();
final byte[] value = entry.getValue();
total += KeyListEncoder.writeLength(key, prev);
total += KeyListEncoder.writeLength(value, null);
prev = key;
}
// Adjusts
total += UnsignedIntEncoder.encodeLength(this.adjusts.size());
prev = null;
for (Map.Entry entry : this.adjusts.entrySet()) {
final byte[] key = entry.getKey();
final long value = entry.getValue();
total += KeyListEncoder.writeLength(key, prev);
total += LongEncoder.encodeLength(value);
prev = key;
}
// Done
return total;
}
/**
* Deserialize a mutable instance created by {@link #serialize serialize()}.
*
*
* Equivalent to {@link #deserialize(InputStream, boolean) deserialize}{@code (input, false)}.
*
* @param input input stream containing data from {@link #serialize serialize()}
* @return mutable deserialized instance
* @throws IllegalArgumentException if {@code input} is null
* @throws IllegalArgumentException if malformed input is detected
* @throws IOException if an I/O error occurs
*/
public static Writes deserialize(InputStream input) throws IOException {
return Writes.deserialize(input, false);
}
/**
* Deserialize an instance created by {@link #serialize serialize()}.
*
* @param input input stream containing data from {@link #serialize serialize()}
* @param immutable true for an immutable instance, otherwise false
* @return deserialized instance
* @throws IllegalArgumentException if {@code input} is null
* @throws IllegalArgumentException if malformed input is detected
* @throws IOException if an I/O error occurs
*/
public static Writes deserialize(InputStream input, boolean immutable) throws IOException {
Preconditions.checkArgument(input != null, "null input");
// Get removes
final KeyRanges removes = new KeyRanges(input, immutable);
// Get puts
final int putCount = UnsignedIntEncoder.read(input);
final byte[][] putKeys = new byte[putCount][];
final byte[][] putVals = new byte[putCount][];
byte[] prev = null;
for (int i = 0; i < putCount; i++) {
putKeys[i] = KeyListEncoder.read(input, prev);
putVals[i] = KeyListEncoder.read(input, null);
prev = putKeys[i];
}
final NavigableMap puts;
if (immutable)
puts = new ImmutableNavigableMap<>(putKeys, putVals, ByteUtil.COMPARATOR);
else {
puts = new TreeMap<>(ByteUtil.COMPARATOR);
for (int i = 0; i < putCount; i++)
puts.put(putKeys[i], putVals[i]);
}
// Get adjusts
final int adjCount = UnsignedIntEncoder.read(input);
final byte[][] adjKeys = new byte[adjCount][];
final Long[] adjVals = new Long[adjCount];
prev = null;
for (int i = 0; i < adjCount; i++) {
adjKeys[i] = KeyListEncoder.read(input, prev);
adjVals[i] = LongEncoder.read(input);
prev = adjKeys[i];
}
final NavigableMap adjusts;
if (immutable)
adjusts = new ImmutableNavigableMap<>(adjKeys, adjVals, ByteUtil.COMPARATOR);
else {
adjusts = new TreeMap<>(ByteUtil.COMPARATOR);
for (int i = 0; i < adjCount; i++)
adjusts.put(adjKeys[i], adjVals[i]);
}
// Done
return new Writes(removes, puts, adjusts, immutable);
}
// Cloneable
/**
* Clone this instance.
*
*
* This is a "mostly deep" clone: all of the mutations are copied, but the actual
* {@code byte[]} keys and values, which are already assumed non-mutable, are not copied.
*
*
* The returned clone will always be mutable, even if this instance is not.
*/
@Override
@SuppressWarnings("unchecked")
public Writes clone() {
final Writes clone;
try {
clone = (Writes)super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
clone.removes = this.removes.clone();
clone.puts = new TreeMap<>(clone.puts);
clone.adjusts = new TreeMap<>(clone.adjusts);
clone.immutable = false;
return clone;
}
/**
* Return an immutable snapshot of this instance.
*
* @return immutable snapshot
*/
public Writes immutableSnapshot() {
if (this.immutable)
return this;
return new Writes(this.removes.immutableSnapshot(),
new ImmutableNavigableMap<>(this.puts), new ImmutableNavigableMap<>(this.adjusts), true);
}
// Object
@Override
public String toString() {
final Converter byteConverter = ByteUtil.STRING_CONVERTER.reverse();
final ConvertedNavigableMap putsView
= new ConvertedNavigableMap<>(this.puts, byteConverter, byteConverter);
final ConvertedNavigableMap adjustsView
= new ConvertedNavigableMap<>(this.adjusts, byteConverter, Converter.identity());
final StringBuilder buf = new StringBuilder();
buf.append(this.getClass().getSimpleName())
.append("[removes=")
.append(this.removes);
if (!this.puts.isEmpty()) {
buf.append(",puts=");
this.appendEntries(buf, putsView);
}
if (!this.adjusts.isEmpty()) {
buf.append(",adjusts=");
this.appendEntries(buf, adjustsView);
}
buf.append("]");
return buf.toString();
}
private void appendEntries(StringBuilder buf, Map map) {
buf.append('{');
int index = 0;
entryLoop:
for (Map.Entry entry : map.entrySet()) {
final String key = entry.getKey();
final Object val = entry.getValue();
switch (index++) {
case 0:
break;
case 32:
buf.append("...");
break entryLoop;
default:
buf.append(", ");
break;
}
buf.append(this.truncate(key, 32))
.append('=')
.append(this.truncate(String.valueOf(val), 32));
}
}
private String truncate(String s, int max) {
if (s == null || s.length() <= max)
return s;
return s.substring(0, max) + "...";
}
}