
io.permazen.core.MapField Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of permazen-core Show documentation
Show all versions of permazen-core Show documentation
Permazen core API classes which provide objects, fields, indexes, queries, and schema management on top of a key/value store.
The newest version!
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package io.permazen.core;
import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import io.permazen.core.util.ObjIdMap;
import io.permazen.encoding.Encoding;
import io.permazen.kv.KVDatabase;
import io.permazen.kv.KVTransaction;
import io.permazen.schema.MapSchemaField;
import io.permazen.schema.SimpleSchemaField;
import io.permazen.util.ByteReader;
import io.permazen.util.ByteWriter;
import io.permazen.util.CloseableIterator;
import io.permazen.util.ImmutableNavigableMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
/**
* Map field.
*
* @param Map key type
* @param Map value type
*/
public class MapField extends ComplexField> {
public static final String KEY_FIELD_NAME = "key";
public static final String VALUE_FIELD_NAME = "value";
final SimpleField keyField;
final SimpleField valueField;
@SuppressWarnings("serial")
MapField(ObjType objType, MapSchemaField field, SimpleField keyField, SimpleField valueField) {
super(objType, field, new TypeToken>() { }
.where(new TypeParameter() { }, keyField.typeToken.wrap())
.where(new TypeParameter() { }, valueField.typeToken.wrap()));
this.keyField = keyField;
this.valueField = valueField;
assert this.keyField.parent == null;
assert this.valueField.parent == null;
this.keyField.parent = this;
this.valueField.parent = this;
}
// Public methods
/**
* Get the key field.
*
* @return map key field
*/
public SimpleField getKeyField() {
return this.keyField;
}
/**
* Get the value field.
*
* @return map value field
*/
public SimpleField getValueField() {
return this.valueField;
}
/**
* Get the index on this fields's {@value #KEY_FIELD_NAME} sub-field.
*
* @return the index on this field's {@value #KEY_FIELD_NAME} sub-field
* @throws UnknownIndexException if there is no index on the {@value #KEY_FIELD_NAME} sub-field
*/
@SuppressWarnings("unchecked")
public MapKeyIndex getMapKeyIndex() {
return (MapKeyIndex)this.keyField.getIndex();
}
/**
* Get the index on this fields's {@value #VALUE_FIELD_NAME} sub-field.
*
* @return the index on this field's {@value #VALUE_FIELD_NAME} sub-field
* @throws UnknownIndexException if there is no index on the {@value #VALUE_FIELD_NAME} sub-field
*/
@SuppressWarnings("unchecked")
public MapValueIndex getMapValueIndex() {
return (MapValueIndex)this.valueField.getIndex();
}
@Override
public List> getSubFields() {
final ArrayList> list = new ArrayList<>(2);
list.add(this.keyField);
list.add(this.valueField);
return list;
}
@Override
@SuppressWarnings("unchecked")
public NavigableMap getValue(Transaction tx, ObjId id) {
Preconditions.checkArgument(tx != null, "null tx");
return (NavigableMap)tx.readMapField(id, this.name, false);
}
/**
* Get the {@code byte[]} key in the underlying key/value store corresponding to this field in the specified object
* and the specified map key.
*
* @param id object ID
* @param key map key
* @return the corresponding {@link KVDatabase} key
* @throws IllegalArgumentException if {@code id} is null or has the wrong object type
* @see KVTransaction#watchKey KVTransaction.watchKey()
*/
public byte[] getKey(ObjId id, K key) {
// Sanity check
Preconditions.checkArgument(id != null, "null id");
// Build key
final ByteWriter writer = new ByteWriter();
writer.write(super.getKey(id));
this.keyField.encoding.write(writer, key);
return writer.getBytes();
}
@Override
public boolean hasDefaultValue(Transaction tx, ObjId id) {
return this.getValue(tx, id).isEmpty();
}
@Override
public String toString() {
return "map field \"" + this.name + "\" containing key "
+ this.keyField.encoding + " and value " + this.valueField.encoding;
}
@Override
public R visit(FieldSwitch target) {
Preconditions.checkArgument(target != null, "null target");
return target.caseMapField(this);
}
// Package Methods
@Override
ComplexSubFieldIndex, ?> createSubFieldIndex(
Schema schema, SimpleSchemaField schemaField, ObjType objType, SimpleField> field) {
Preconditions.checkArgument(schemaField != null, "null schemaField");
Preconditions.checkArgument(field != null, "null field");
if (field == this.keyField)
return new MapKeyIndex<>(schema, schemaField, objType, this);
if (field == this.valueField)
return new MapValueIndex<>(schema, schemaField, objType, this);
throw new IllegalArgumentException("wrong sub-field");
}
@Override
@SuppressWarnings("unchecked")
Iterable iterateSubField(Transaction tx, ObjId id, SimpleField subField) {
Preconditions.checkArgument(subField != null, "null subField");
if (subField == this.keyField)
return (Iterable)this.getValue(tx, id).keySet();
if (subField == this.valueField)
return (Iterable)this.getValue(tx, id).values();
throw new IllegalArgumentException("unknown sub-field");
}
@Override
NavigableMap getValueInternal(Transaction tx, ObjId id) {
return new JSMap<>(tx, this, id);
}
@Override
NavigableMap getValueReadOnlyCopy(Transaction tx, ObjId id) {
return new ImmutableNavigableMap<>(this.getValueInternal(tx, id));
}
@Override
void copy(ObjId srcId, ObjId dstId, Transaction srcTx, Transaction dstTx, ObjIdMap objectIdMap) {
final Encoding keyEncoding = this.keyField.encoding;
final NavigableMap src = this.getValue(srcTx, srcId);
final NavigableMap dst = this.getValue(dstTx, dstId);
try (CloseableIterator> si = CloseableIterator.wrap(src.entrySet().iterator());
CloseableIterator> di = CloseableIterator.wrap(dst.entrySet().iterator())) {
// Check for empty
if (!si.hasNext()) {
dst.clear();
return;
}
// If we're not remapping anything, walk forward through both maps and synchronize dst to src
if (objectIdMap == null || objectIdMap.isEmpty()
|| (!this.keyField.remapsObjectId() && !this.valueField.remapsObjectId())) {
if (!di.hasNext()) {
dst.putAll(src);
return;
}
Map.Entry s = si.next();
Map.Entry d = di.next();
while (true) {
final int diff = keyEncoding.compare(s.getKey(), d.getKey());
boolean sadvance = true;
boolean dadvance = true;
if (diff < 0) {
dst.put(s.getKey(), s.getValue());
dadvance = false;
} else if (diff > 0) {
di.remove();
sadvance = false;
} else
d.setValue(s.getValue());
if (sadvance) {
if (!si.hasNext()) {
dst.tailMap(s.getKey(), false).clear();
return;
}
s = si.next();
}
if (dadvance) {
if (!di.hasNext()) {
dst.putAll(src.tailMap(s.getKey(), true));
return;
}
d = di.next();
}
}
} else {
dst.clear();
while (si.hasNext()) {
final Map.Entry entry = si.next();
final K destKey = this.keyField.remapObjectId(objectIdMap, entry.getKey());
final V destValue = this.valueField.remapObjectId(objectIdMap, entry.getValue());
dst.put(destKey, destValue);
}
}
}
}
@Override
void buildIndexEntry(ObjId id, SimpleField> subField, ByteReader reader, byte[] value, ByteWriter writer) {
if (subField == this.keyField) {
writer.write(reader);
id.writeTo(writer);
} else if (subField == this.valueField) {
writer.write(value);
id.writeTo(writer);
writer.write(reader);
} else
throw new RuntimeException("internal error");
}
@Override
void unreferenceRemovedTypes(Transaction tx, ObjId id, ReferenceField subField, Set removedStorageIds) {
assert subField == this.keyField || subField == this.valueField;
for (Iterator> i = this.getValueInternal(tx, id).entrySet().iterator(); i.hasNext(); ) {
final Map.Entry entry = i.next();
final ObjId ref = subField == this.keyField ? (ObjId)entry.getKey() : (ObjId)entry.getValue();
if (ref != null && removedStorageIds.contains(ref.getStorageId()))
i.remove();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy