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

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