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

convex.core.data.MapEntry Maven / Gradle / Ivy

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 keyRef, Ref 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 mapper) {
		return Vectors.of(mapper.apply(getKey()), mapper.apply(getValue()));
	}

	@Override
	public  R reduce(BiFunction 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 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 });
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy