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

io.permazen.core.MapField Maven / Gradle / Ivy

Go to download

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