convex.core.data.MapEntry Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
The newest version!
package convex.core.data;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import convex.core.data.type.AType;
import convex.core.data.type.Types;
import convex.core.exceptions.InvalidDataException;
import convex.core.lang.RT;
import convex.core.util.Errors;
import convex.core.util.Utils;
/**
* Map.Entry implementation for persistent maps. This is primarily intended as an efficient
* implementation class for handling entries in Convex maps, and also to support the Java Map.Entry
* interface for compatibility and developer convenience.
*
* From a CVM perspective, a MapEntry is just a regular 2 element Vector. As such, MapEntry is *not* canonical
* and getting the canonical form of a MapEntry requires converting to a Vector
*
* Contains exactly 2 elements, one for key and one for value
*
* Implements Comparable using the Hash value of keys.
*
* @param The type of keys
* @param The type of values
*/
public class MapEntry extends AMapEntry implements Comparable> {
private final Ref keyRef;
private final Ref valueRef;
private MapEntry(Ref key, Ref value) {
super(2);
this.keyRef = key;
this.valueRef = value;
}
@Override
public AType getType() {
return Types.VECTOR;
}
@SuppressWarnings("unchecked")
public static MapEntry createRef(Ref extends K> keyRef, Ref extends V> valueRef) {
// ensure we have a hash at least
return new MapEntry((Ref) keyRef, (Ref) valueRef);
}
/**
* Creates a new MapEntry with the provided key and value
* @param Type of Key
* @param Type of value
* @param key Key to use for MapEntry
* @param value Value to use for MapEntry
* @return New MapEntry instance
*/
public static MapEntry create(K key, V value) {
return createRef(Ref.get(key), Ref.get(value));
}
/**
* Create a map entry, converting key and value to correct CVM types.
* @param Type of Keys
* @param Type of Values
* @param key Key to use for map entry
* @param value Value to use for map entry
* @return New MapEntry
*/
public static MapEntry of(Object key, Object value) {
return create(RT.cvm(key),RT.cvm(value));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static MapEntry convertOrNull(AVector v) {
if (v.count()!=2) return null;
return createRef(v.getElementRef(0),v.getElementRef(1));
}
@Override
public MapEntry withValue(V value) {
if (value == getValue()) return this;
return new MapEntry(keyRef, Ref.get(value));
}
@SuppressWarnings("unchecked")
@Override
public MapEntry assoc(long i, ACell a) {
if (i == 0) return withKey((K) a);
if (i == 1) return withValue((V) a);
return null;
}
@Override
protected MapEntry withKey(K key) {
if (key == getKey()) return this;
return new MapEntry(Ref.get(key), valueRef);
}
@Override
public K getKey() {
return keyRef.getValue();
}
@Override
public AVector map(Function super ACell, ? extends R> mapper) {
return Vectors.of(mapper.apply(getKey()), mapper.apply(getValue()));
}
@Override
public R reduce(BiFunction super R, ? super ACell, ? extends R> func, R value) {
R result = func.apply(value, getKey());
result = func.apply(result, getKey());
return result;
}
@SuppressWarnings("unchecked")
@Override
protected void copyToArray(R[] arr, int offset) {
arr[offset] = (R) getKey();
arr[offset + 1] = (R) getValue();
}
/**
* Gets the Hash of the key for this {@linkplain MapEntry}
*
* @return the Hash of the Key
*/
public Hash getKeyHash() {
return getKeyRef().getHash();
}
@Override
public V getValue() {
return valueRef.getValue();
}
public Ref getKeyRef() {
return keyRef;
}
public Ref getValueRef() {
return valueRef;
}
@Override
public int compareTo(MapEntry o) {
if (this == o) return 0;
return keyRef.compareTo(o.keyRef);
}
@Override
public final int getRefCount() {
return 2;
}
@SuppressWarnings("unchecked")
@Override
public Ref getRef(int i) {
if ((i >> 1) != 0) throw new IndexOutOfBoundsException(i);
return (Ref) ((i == 0) ? keyRef : valueRef);
}
@SuppressWarnings("unchecked")
@Override
public MapEntry updateRefs(IRefFunction func) {
Ref newKeyRef = (Ref) func.apply(keyRef);
Ref newValueRef = (Ref) func.apply(valueRef);
// Keep this instance if no change
if ((keyRef == newKeyRef) && (valueRef == newValueRef)) return this;
MapEntry result= new MapEntry(newKeyRef, newValueRef);
result.attachEncoding(encoding); // this is an optimisation to avoid re-encoding
return result;
}
@Override
public boolean equals(AVector super ACell> o) {
if (o==null) return false;
if (o==this) return true;
AVector> v=(AVector>) o;
if (v.count()!=2) return false;
return getEncoding().equals(o.getEncoding());
}
public boolean equals(MapEntry b) {
if (this == b) return true;
return keyRef.equals(b.keyRef) && valueRef.equals(b.valueRef);
}
/**
* Checks if the keys of two map entries are equal
*
* @param b MapEntry to compare with this MapEntry
* @return true if this entry's key equals that of the other entry, false
* otherwise.
*/
public boolean keyEquals(MapEntry b) {
return keyRef.equals(b.keyRef);
}
@SuppressWarnings("unchecked")
@Override
public AVector toVector() {
return new VectorLeaf(new Ref[] { keyRef, valueRef });
}
@Override
public boolean contains(Object o) {
return (Utils.equals(o, getKey()) || Utils.equals(o, getValue()));
}
@Override
public ACell get(long i) {
if (i == 0) return getKey();
if (i == 1) return getValue();
throw new IndexOutOfBoundsException(Errors.badIndex(i));
}
@SuppressWarnings("unchecked")
@Override
public Ref getElementRef(long i) {
if (i == 0) return (Ref) keyRef;
if (i == 1) return (Ref) valueRef;
throw new IndexOutOfBoundsException(Errors.badIndex(i));
}
@SuppressWarnings("unchecked")
@Override
protected Ref getElementRefUnsafe(long i) {
if (i == 0) return (Ref) keyRef;
return (Ref) valueRef;
}
@Override
public int encode(byte[] bs, int pos) {
bs[pos++]=Tag.VECTOR;
return encodeRaw(bs,pos);
}
/**
* Writes the raw MapEntry content. Puts the key and value Refs onto the given
* ByteBuffer
*
* @param bs Byte array to write to
* @return Updated position after writing
*/
@Override
public int encodeRaw(byte[] bs, int pos) {
pos = Format.writeVLCCount(bs,pos, 2); // Size of 2, to match VectorLeaf encoding
return encodeRefs(bs,pos);
}
int encodeRefs(byte[] bs, int pos) {
pos = keyRef.encode(bs,pos);
pos = valueRef.encode(bs,pos);
return pos;
}
/**
* Writes a MapEntry or null content in compressed format (no count). Useful for
* embedding an optional MapEntry inside a larger Encoding
*
* @param me MapEntry to encode
* @param bs Byte array to write to
* @param pos Starting position for encoding in byte array
* @return Updated position after writing
*/
public static int encodeCompressed(MapEntry,?> me,byte[] bs, int pos) {
if (me==null) {
bs[pos++]=Tag.NULL;
} else {
bs[pos++]=Tag.VECTOR;
pos = me.encodeRefs(bs,pos);
}
return pos;
}
@Override
public int estimatedEncodingSize() {
return 2+Format.MAX_EMBEDDED_LENGTH*2; // header plus count two embedded objects
}
@SuppressWarnings("unchecked")
@Override
public void visitElementRefs(Consumer> f) {
f.accept((Ref) keyRef);
f.accept((Ref) valueRef);
}
@Override
public AVector concat(ASequence> b) {
if (b.isEmpty()) return this;
return toVector().concat(b);
}
@Override
public void validate() throws InvalidDataException {
super.validate();
keyRef.validate();
valueRef.validate();
if (!Cells.isCVM(getKey())) throw new InvalidDataException("MapEntry key not a CVM value: " +getKey(),this);
if (!Cells.isCVM(getValue())) throw new InvalidDataException("MapEntry value not a CVM value: " +getValue(),this);
}
@Override
public void validateCell() throws InvalidDataException {
// TODO: is there really Nothing to do?
}
@Override
public byte getTag() {
return Tag.VECTOR;
}
@Override
public boolean isCanonical() {
// TODO: probably should be canonical?
return false;
}
@SuppressWarnings("unchecked")
@Override
public VectorLeaf toCanonical() {
return new VectorLeaf(new Ref[] { keyRef, valueRef });
}
}