
io.permazen.core.MapField Maven / Gradle / Ivy
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package io.permazen.core;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import io.permazen.core.util.ObjIdMap;
import io.permazen.util.ByteReader;
import io.permazen.util.ByteWriter;
import io.permazen.util.CloseableIterator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.SortedSet;
import java.util.TreeMap;
/**
* 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;
/**
* Constructor.
*
* @param objType the object type that contains this field
* @param name the name of the field
* @param storageId field content storage ID
* @param schema schema version
* @param keyField this field's key sub-field
* @param valueField this field's value sub-field
* @throws IllegalArgumentException if any parameter is null
* @throws IllegalArgumentException if {@code storageId} is non-positive
*/
@SuppressWarnings("serial")
MapField(String name, int storageId, Schema schema, SimpleField keyField, SimpleField valueField) {
super(name, storageId, schema, 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;
}
@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.storageId, false);
}
@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.fieldType + " and value " + this.valueField.fieldType;
}
@Override
public R visit(FieldSwitch target) {
return target.caseMapField(this);
}
// Non-public methods
@Override
@SuppressWarnings("unchecked")
Iterable iterateSubField(Transaction tx, ObjId id, SimpleField 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 Maps.unmodifiableNavigableMap(new TreeMap(this.getValueInternal(tx, id)));
}
@Override
ComplexSubFieldStorageInfo, ?> toStorageInfo(SimpleField> subField) {
if (subField == this.keyField)
return new MapKeyStorageInfo(this);
if (subField == this.valueField)
return new MapValueStorageInfo(this);
throw new IllegalArgumentException("unknown sub-field");
}
@Override
void copy(ObjId srcId, ObjId dstId, Transaction srcTx, Transaction dstTx, ObjIdMap objectIdMap) {
final FieldType keyFieldType = this.keyField.fieldType;
final NavigableMap src = this.getValue(srcTx, srcId);
final NavigableMap dst = this.getValue(dstTx, dstId);
try (final CloseableIterator> si = CloseableIterator.wrap(src.entrySet().iterator());
final 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 = keyFieldType.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, SortedSet 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();
}
}
@Override
boolean isUpgradeCompatible(Field> field) {
if (field.getClass() != this.getClass())
return false;
final MapField, ?> that = (MapField, ?>)field;
return this.keyField.isUpgradeCompatible(that.keyField)
&& this.valueField.isUpgradeCompatible(that.valueField);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy