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

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

The newest version!
package convex.core.data;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

import convex.core.exceptions.BadFormatException;
import convex.core.exceptions.InvalidDataException;
import convex.core.exceptions.TODOException;
import convex.core.exceptions.Panic;
import convex.core.util.Bits;
import convex.core.util.MergeFunction;
import convex.core.util.Utils;

/**
 * Persistent Map for large hash maps requiring tree structure.
 * 
 * Internally implemented as a radix tree, indexed by key hash. Uses an array of
 * child Maps, with a bitmap mask indicating which hex digits are present, i.e.
 * have non-empty children.
 *
 * @param  Type of map keys
 * @param  Type of map values
 */
public class MapTree extends AHashMap {
	/**
	 * Child maps, one for each present bit in the mask, max 16
	 */
	private final Ref>[] children;
	
	private static final int FANOUT=16;

	/**
	 * Shift position of this treemap node in number of hex digits
	 */
	private final int shift;

	/**
	 * Mask indicating which hex digits are present in the child array e.g. 0x0001
	 * indicates all children are in the '0' digit. e.g. 0xFFFF indicates there are
	 * children for every digit.
	 */
	private final short mask;

	private MapTree(Ref>[] blocks, int shift, short mask, long count) {
		super(count);
		this.children = blocks;
		this.shift = shift;
		this.mask = mask;
	}

	/**
	 * Computes the total count from an array of Refs to maps Ignores null Refs in
	 * child array
	 * 
	 * @param children
	 * @return The total count of all child maps
	 */
	private static  long computeCount(Ref>[] children) {
		long n = 0;
		for (Ref> cref : children) {
			if (cref == null) continue;
			AMap m = cref.getValue();
			n += m.count();
		}
		return n;
	}

	@SuppressWarnings("unchecked")
	public static  MapTree create(MapEntry[] newEntries, int shift) {
		int n = newEntries.length;
		if (n <= MapLeaf.MAX_ENTRIES) {
			throw new IllegalArgumentException(
					"Insufficient distinct entries for TreeMap construction: " + newEntries.length);
		}

		// construct full child array
		Ref>[] children = new Ref[FANOUT];
		for (int i = 0; i < n; i++) {
			MapEntry e = newEntries[i];
			int ix = e.getKeyHash().getHexDigit(shift);
			Ref> ref = children[ix];
			if (ref == null) {
				children[ix] = MapLeaf.create(e).getRef();
			} else {
				AHashMap newChild=ref.getValue().assocEntry(e, shift + 1);
				children[ix] = newChild.getRef();
			}
		}
		return (MapTree) createFull(children, shift);
	}

	/**
	 * Creates a Tree map given child refs for each digit
	 * 
	 * @param children An array of children, may refer to nulls or empty maps which
	 *                 will be filtered out
	 * @return
	 */
	private static  AHashMap createFull(Ref>[] children, int shift, long count) {
		if (children.length != FANOUT) throw new IllegalArgumentException("16 children required!");
		Ref>[] newChildren = Utils.filterArray(children, a -> {
			if (a == null) return false;
			AMap m = a.getValue();
			return ((m != null) && !m.isEmpty());
		});

		if (children != newChildren) {
			return create(newChildren, shift, Utils.computeMask(children, newChildren), count);
		} else {
			return create(children, shift, (short) 0xFFFF, count);
		}
	}

	/**
	 * Create a MapTree with a full compliment of children.
	 * @param 
	 * @param 
	 * @param newChildren
	 * @param shift
	 * @return
	 */
	private static  AHashMap createFull(Ref>[] newChildren, int shift) {
		return createFull(newChildren, shift, computeCount(newChildren));
	}

	/**
	 * Creates a Map with the specified child map Refs. Removes empty maps passed as
	 * children.
	 * 
	 * Returns a ListMap for small maps.
	 * 
	 * @param children Array of Refs to child maps for each bit in mask
	 * @param shift    Shift position (hex digit of key hashes for this map)
	 * @param mask     Mask specifying the hex digits included in the child array at
	 *                 this shift position
	 * @return A new map as specified @
	 */
	@SuppressWarnings("unchecked")
	private static  AHashMap create(Ref>[] children, int shift, short mask, long count) {
		int cLen = children.length;
		if (Integer.bitCount(mask & 0xFFFF) != cLen) {
			throw new IllegalArgumentException(
					"Invalid child array length " + cLen + " for bit mask " + Utils.toHexString(mask));
		}

		// compress small counts to ListMap
		if (count <= MapLeaf.MAX_ENTRIES) {
			MapEntry[] entries = new MapEntry[Utils.checkedInt(count)];
			int ix = 0;
			for (Ref> childRef : children) {
				AMap child = childRef.getValue();
				long cc = child.count();
				for (long i = 0; i < cc; i++) {
					entries[ix++] = child.entryAt(i);
				}
			}
			assert (ix == count);
			return MapLeaf.create(entries);
		}
		int sel = (1 << cLen) - 1;
		short newMask = mask;
		for (int i = 0; i < cLen; i++) {
			AMap child = children[i].getValue();
			if (child.isEmpty()) {
				newMask = (short) (newMask & ~(1 << digitForIndex(i, mask))); // remove from mask
				sel = sel & ~(1 << i); // remove from selection
			}
		}
		if (mask != newMask) {
			return new MapTree(Utils.filterSmallArray(children, sel), shift, newMask, count);
		}
		return new MapTree(children, shift, mask, count);
	}

	@Override
	public MapEntry getEntry(ACell k) {
		return getKeyRefEntry(Ref.get(k));
	}

	@Override
	public MapEntry getKeyRefEntry(Ref ref) {
		Hash h=ref.getHash();
		int digit = h.getHexDigit(shift);
		int i = Bits.indexForDigit(digit, mask);
		if (i < 0) return null; // -1 case indicates not found
		return children[i].getValue().getKeyRefEntry(ref);
	}

	@Override
	public boolean containsValue(ACell value) {
		for (Ref> b : children) {
			if (b.getValue().containsValue(value)) return true;
		}
		return false;
	}

	@Override
	public V get(ACell key) {
		MapEntry me = getKeyRefEntry(Ref.get(key));
		if (me == null) return null;
		return me.getValue();
	}

	@Override
	public MapEntry entryAt(long i) {
		long pos = i;
		for (Ref> c : children) {
			AHashMap child = c.getValue();
			long cc = child.count();
			if (pos < cc) return child.entryAt(pos);
			pos -= cc;
		}
		throw new IndexOutOfBoundsException((int)i);
	}

	@Override
	protected MapEntry getEntryByHash(Hash hash) {
		int digit = hash.getHexDigit(shift);
		int i = Bits.indexForDigit(digit, mask);
		if (i < 0) return null; // not present
		return children[i].getValue().getEntryByHash(hash);
	}

	@SuppressWarnings("unchecked")
	@Override
	public AHashMap dissoc(ACell key) {
		return dissocRef((Ref) Ref.get(key));
	}

	@Override
	@SuppressWarnings("unchecked")
	public AHashMap dissocRef(Ref keyRef) {
		int digit = keyRef.getHash().getHexDigit(shift);
		int i = Bits.indexForDigit(digit, mask);
		if (i < 0) return this; // not present

		// dissoc entry from child
		AHashMap child = children[i].getValue();
		AHashMap newChild = child.dissocRef(keyRef);
		if (child == newChild) return this; // no removal, no change

		if (count - 1 == MapLeaf.MAX_ENTRIES) {
			// reduce to a ListMap
			HashSet> eset = entrySet();
			boolean removed = eset.removeIf(e -> Utils.equals(((MapEntry) e).getKeyRef(), keyRef));
			if (!removed) throw new Panic("Expected to remove at least one entry!");
			return MapLeaf.create(eset.toArray((MapEntry[]) MapLeaf.EMPTY_ENTRIES));
		} else {
			// replace child
			if (newChild.isEmpty()) return dissocChild(i);
			return replaceChild(i, newChild.getRef());
		}
	}

	@SuppressWarnings("unchecked")
	private AHashMap dissocChild(int i) {
		int bsize = children.length;
		AHashMap child = children[i].getValue();
		Ref>[] newBlocks = (Ref>[]) new Ref[bsize - 1];
		System.arraycopy(children, 0, newBlocks, 0, i);
		System.arraycopy(children, i + 1, newBlocks, i, bsize - i - 1);
		short newMask = (short) (mask & (~(1 << digitForIndex(i, mask))));
		long newCount = count - child.count();
		return create(newBlocks, shift, newMask, newCount);
	}

	@SuppressWarnings("unchecked")
	private MapTree insertChild(int digit, Ref> newChild) {
		int bsize = children.length;
		int i = Bits.positionForDigit(digit, mask);
		short newMask = (short) (mask | (1 << digit));
		if (mask == newMask) throw new Panic("Digit already present!");

		Ref>[] newChildren = (Ref>[]) new Ref[bsize + 1];
		System.arraycopy(children, 0, newChildren, 0, i);
		System.arraycopy(children, i, newChildren, i + 1, bsize - i);
		newChildren[i] = newChild;
		long newCount = count + newChild.getValue().count();
		return (MapTree) create(newChildren, shift, newMask, newCount);
	}

	/**
	 * Replaces the child ref at a given index position. Will return the same
	 * TreeMap if no change
	 * 
	 * @param i
	 * @param newChild
	 * @return @
	 */
	private MapTree replaceChild(int i, Ref> newChild) {
		if (children[i] == newChild) return this;
		AHashMap oldChild = children[i].getValue();
		Ref>[] newChildren = children.clone();
		newChildren[i] = newChild;
		long newCount = count + newChild.getValue().count() - oldChild.count();
		return (MapTree) create(newChildren, shift, mask, newCount);
	}

	public static int digitForIndex(int index, short mask) {
		// scan mask for specified index
		int found = 0;
		for (int i = 0; i < FANOUT; i++) {
			if ((mask & (1 << i)) != 0) {
				if (found++ == index) return i;
			}
		}
		throw new IllegalArgumentException("Index " + index + " not available in mask map: " + Utils.toHexString(mask));
	}

	@SuppressWarnings("unchecked")
	@Override
	public MapTree assoc(ACell key, ACell value) {
		K k= (K)key;
		Ref keyRef = Ref.get(k);
		return assocRef(keyRef, (V) value, shift);
	}

	@Override
	public MapTree assocRef(Ref keyRef, V value) {
		return assocRef(keyRef, value, shift);
	}

	@Override
	protected MapTree assocRef(Ref keyRef, V value, int shift) {
		if (this.shift != shift) {
			throw new Panic("Invalid shift!");
		}
		int digit = keyRef.getHash().getHexDigit(shift);
		int i = Bits.indexForDigit(digit, mask);
		if (i < 0) {
			// location not present, need to insert new child
			AHashMap newChild = MapLeaf.create(MapEntry.createRef(keyRef, Ref.get(value)));
			return insertChild(digit, newChild.getRef());
		} else {
			// child exists, so assoc in new ref at lower shift level
			AHashMap child = children[i].getValue();
			AHashMap newChild = child.assocRef(keyRef, value, shift + 1);
			return replaceChild(i, newChild.getRef());
		}
	}

	@Override
	public AHashMap assocEntry(MapEntry e) {
		assert (this.shift == 0); // should never call this on a different shift
		return assocEntry(e, 0);
	}

	@Override
	public MapTree assocEntry(MapEntry e, int shift) {
		assert (this.shift == shift); // should always be correct shift
		Ref keyRef = e.getKeyRef();
		int digit = keyRef.getHash().getHexDigit(shift);
		int i = Bits.indexForDigit(digit, mask);
		if (i < 0) {
			// location not present
			AHashMap newChild = MapLeaf.create(e);
			return insertChild(digit, newChild.getRef());
		} else {
			// location needs update
			AHashMap child = children[i].getValue();
			AHashMap newChild = child.assocEntry(e, shift + 1);
			if (child == newChild) return this;
			return replaceChild(i, newChild.getRef());
		}
	}

	@Override
	public Set keySet() {
		int len = size();
		HashSet h = new HashSet(len);
		accumulateKeySet(h);
		return h;
	}

	@Override
	protected void accumulateKeySet(Set h) {
		for (Ref> mr : children) {
			mr.getValue().accumulateKeySet(h);
		}
	}

	@Override
	protected void accumulateValues(java.util.List al) {
		for (Ref> mr : children) {
			mr.getValue().accumulateValues(al);
		}
	}

	@Override
	protected void accumulateEntrySet(Set> h) {
		for (Ref> mr : children) {
			mr.getValue().accumulateEntrySet(h);
		}
	}

	@Override
	public int encode(byte[] bs, int pos) {
		bs[pos++]=Tag.MAP;
		return encodeRaw(bs,pos);
	}

	
	@Override
	public int encodeRaw(byte[] bs, int pos) {
		int ilength = children.length;
		pos = Format.writeVLCLong(bs,pos, count); // TODO: Count instead?
		
		bs[pos++] = (byte) shift;
		pos = Utils.writeShort(bs, pos,mask);

		for (int i = 0; i < ilength; i++) {
			pos = children[i].encode(bs,pos);
		}
		return pos;
	}

	@Override
	public int estimatedEncodingSize() {
		// allow space for tag, shift byte byte, 2 byte mask, embedded child refs
		return 4 + Format.MAX_EMBEDDED_LENGTH * children.length;
	}
	
	/**
	 * Max length is tag, shift byte, 2 byte mask, max count plus embedded Refs
	 */
	public static int MAX_ENCODING_LENGTH = 4 + Format.MAX_EMBEDDED_LENGTH * FANOUT;

	/**
	 * Reads a ListMap from the provided Blob 
	 * 
	 * @param b Blob to read from
	 * @param pos Start position in Blob (location of tag byte)
	 * @param count Count of map entries* 
	 * @return New decoded instance
	 * @throws BadFormatException In the event of any encoding error
	 */
	@SuppressWarnings("unchecked")
	public static  MapTree read(Blob b, int pos, long count) throws BadFormatException {
		int epos=pos+1+Format.getVLCLength(count);
		int shift=b.byteAt(epos);
		short mask=b.shortAt(epos+1);
		epos+=3;

		int ilength = Integer.bitCount(mask & 0xFFFF);
		Ref>[] blocks = (Ref>[]) new Ref[ilength];

		for (int i = 0; i < ilength; i++) {
			// need to read as a Ref
			Ref> ref = Format.readRef(b,epos);
			epos+=ref.getEncodingLength();
			blocks[i] = ref;
		}
		// create directly, we have all values
		MapTree result = new MapTree(blocks, shift, mask, count);
		if (!result.isValidStructure()) throw new BadFormatException("Problem with TreeMap invariants");
		result.attachEncoding(b.slice(pos, epos));
		return result;
	}

	@Override
	public void forEach(BiConsumer action) {
		for (Ref> sub : children) {
			sub.getValue().forEach(action);
		}
	}

	@Override
	public boolean isCanonical() {
		if (count <= MapLeaf.MAX_ENTRIES) return false;
		return true;
	}
	
	@Override public final boolean isCVMValue() {
		// A MapTree is only a valid CVM value at the top level (shift == 0)
		return (shift==0);
	}
	
	@Override public final boolean isDataValue() {
		// A MapTree is only a valid data value at the top level (shift == 0)
		return (shift==0);
	}

	@Override
	public int getRefCount() {
		return children.length;
	}

	@SuppressWarnings("unchecked")
	@Override
	public  Ref getRef(int i) {
		return (Ref) children[i];
	}

	@SuppressWarnings("unchecked")
	@Override
	public MapTree updateRefs(IRefFunction func) {
		int n = children.length;
		if (n == 0) return this;
		Ref>[] newChildren = children;
		for (int i = 0; i < n; i++) {
			Ref> child = children[i];
			Ref> newChild = (Ref>) func.apply(child);
			if (child != newChild) {
				if (children == newChildren) {
					newChildren = children.clone();
				}
				newChildren[i] = newChild;
			}
		}
		if (newChildren == children) return this;
		// Note: we assume no key hashes have changed, so structure is the same
		MapTree result= new MapTree<>(newChildren, shift, mask, count);
		result.attachEncoding(encoding); // this is an optimisation to avoid re-encoding
		return result;
	}

	@Override
	public AHashMap mergeWith(AHashMap b, MergeFunction func) {
		return mergeWith(b, func, this.shift);
	}

	@Override
	protected AHashMap mergeWith(AHashMap b, MergeFunction func, int shift) {
		if ((b instanceof MapTree)) {
			MapTree bt = (MapTree) b;
			if (this.shift != bt.shift) throw new Panic("Misaligned shifts!");
			return mergeWith(bt, func, shift);
		}
		if ((b instanceof MapLeaf)) return mergeWith((MapLeaf) b, func, shift);
		throw new Panic("Unrecognised map type: " + b.getClass());
	}

	@SuppressWarnings("unchecked")
	private AHashMap mergeWith(MapTree b, MergeFunction func, int shift) {
		// assume two TreeMaps with identical prefix and shift
		assert (b.shift == shift);
		int fullMask = mask | b.mask;
		// We are going to build full child list only if needed
		Ref>[] newChildren = null;
		for (int digit = 0; digit < FANOUT; digit++) {
			int bitMask = 1 << digit;
			if ((fullMask & bitMask) == 0) continue; // nothing to merge at this index
			AHashMap ac = childForDigit(digit).getValue();
			AHashMap bc = b.childForDigit(digit).getValue();
			AHashMap rc = ac.mergeWith(bc, func, shift + 1);
			if (ac != rc) {
				if (newChildren == null) {
					newChildren = (Ref>[]) new Ref[FANOUT];
					for (int ii = 0; ii < digit; ii++) { // copy existing children up to this point
						int chi = Bits.indexForDigit(ii, mask);
						if (chi >= 0) newChildren[ii] = children[chi];
					}
				}
			}
			if (newChildren != null) newChildren[digit] = rc.getRef();
		}
		if (newChildren == null) return this;
		return createFull(newChildren, shift);
	}

	@SuppressWarnings("unchecked")
	private AHashMap mergeWith(MapLeaf b, MergeFunction func, int shift) {
		Ref>[] newChildren = null;
		int ix = 0;
		for (int i = 0; i < FANOUT; i++) {
			int imask = (1 << i); // mask for this digit
			if ((mask & imask) == 0) continue;
			Ref> cref = children[ix++];
			AHashMap child = cref.getValue();
			MapLeaf bSubset = b.filterHexDigits(shift, imask); // filter only relevant elements in b
			AHashMap newChild = child.mergeWith(bSubset, func, shift + 1);
			if (child != newChild) {
				if (newChildren == null) {
					newChildren = (Ref>[]) new Ref[16];
					for (int ii = 0; ii < children.length; ii++) { // copy existing children
						int chi = digitForIndex(ii, mask);
						newChildren[chi] = children[ii];
					}
				}
			}
			if (newChildren != null) {
				newChildren[i] = newChild.getRef();
			}
		}
		assert (ix == children.length);
		// if any new children created, create a new Map, else use this
		AHashMap result = (newChildren == null) ? this : createFull(newChildren, shift);

		MapLeaf extras = b.filterHexDigits(shift, ~mask);
		int en = extras.size();
		for (int i = 0; i < en; i++) {
			MapEntry e = extras.entryAt(i);
			V value = func.merge(null, e.getValue());
			if (value != null) {
				// include only new keys where function result is not null. Re-use existing
				// entry if possible.
				result = result.assocEntry(e.withValue(value), shift);
			}
		}
		return result;
	}

	@Override
	public AHashMap mergeDifferences(AHashMap b, MergeFunction func) {
		return mergeDifferences(b, func,0);
	}
	
	@Override
	protected AHashMap mergeDifferences(AHashMap b, MergeFunction func,int shift) {
		if ((b instanceof MapTree)) {
			MapTree bt = (MapTree) b;
			// this is OK, top levels should both have shift 0 and be aligned down the tree.
			if (this.shift != bt.shift) throw new Panic("Misaligned shifts!");
			return mergeDifferences(bt, func,shift);
		} else {
			// must be ListMap
			return mergeDifferences((MapLeaf) b, func,shift);
		}
	}

	@SuppressWarnings("unchecked")
	private AHashMap mergeDifferences(MapTree b, MergeFunction func, int shift) {
		// assume two treemaps with identical prefix and shift
		if (this.equals(b)) return this; // no differences to merge
		int fullMask = mask | b.mask;
		Ref>[] newChildren = null; // going to build new full child list if needed
		for (int i = 0; i < FANOUT; i++) {
			int bitMask = 1 << i;
			if ((fullMask & bitMask) == 0) continue; // nothing to merge at this index
			Ref> aref = childForDigit(i);
			Ref> bref = b.childForDigit(i);
			if (aref.equals(bref)) continue; // identical children, no differences
			AHashMap ac = aref.getValue();
			AHashMap bc = bref.getValue();
			AHashMap newChild = ac.mergeDifferences(bc, func,shift+1);
			if (newChild != ac) {
				if (newChildren == null) {
					newChildren = (Ref>[]) new Ref[16];
					for (int ii = 0; ii < 16; ii++) { // copy existing children
						int chi = Bits.indexForDigit(ii, mask);
						if (chi >= 0) newChildren[ii] = children[chi];
					}
				}
			}
			if (newChildren != null) newChildren[i] = (newChild == bc) ? bref : newChild.getRef();
		}
		if (newChildren == null) return this;
		return createFull(newChildren, shift);
	}

	@SuppressWarnings("unchecked")
	private AHashMap mergeDifferences(MapLeaf b, MergeFunction func, int shift) {
		Ref>[] newChildren = null;
		int ix = 0;
		for (int i = 0; i < FANOUT; i++) {
			int imask = (1 << i); // mask for this digit
			if ((mask & imask) == 0) continue;
			Ref> cref = children[ix++];
			AHashMap child = cref.getValue();
			MapLeaf bSubset = b.filterHexDigits(shift, imask); // filter only relevant elements in b
			AHashMap newChild = child.mergeDifferences(bSubset, func,shift+1);
			if (child != newChild) {
				if (newChildren == null) {
					newChildren = (Ref>[]) new Ref[16];
					for (int ii = 0; ii < children.length; ii++) { // copy existing children
						int chi = digitForIndex(ii, mask);
						newChildren[chi] = children[ii];
					}
				}
			}
			if (newChildren != null) newChildren[i] = newChild.getRef();
		}
		assert (ix == children.length);
		AHashMap result = (newChildren == null) ? this : createFull(newChildren, shift);

		MapLeaf extras = b.filterHexDigits(shift, ~mask);
		int en = extras.size();
		for (int i = 0; i < en; i++) {
			MapEntry e = extras.entryAt(i);
			V value = func.merge(null, e.getValue());
			if (value != null) {
				// include only new keys where function result is not null. Re-use existing
				// entry if possible.
				result = result.assocEntry(e.withValue(value), shift);
			}
		}
		return result;
	}

	/**
	 * Gets the Ref for the child at the given digit, or an empty map if not found
	 * 
	 * @param digit The hex digit to query at this TreeMap's shift position
	 * @return The child map for this digit, or an empty map if the child does not
	 *         exist
	 */
	private Ref> childForDigit(int digit) {
		int ix = Bits.indexForDigit(digit, mask);
		if (ix < 0) return Maps.emptyRef();
		return children[ix];
	}

	@Override
	public  R reduceValues(BiFunction func, R initial) {
		int n = children.length;
		R result = initial;
		for (int i = 0; i < n; i++) {
			result = children[i].getValue().reduceValues(func, result);
		}
		return result;
	}

	@Override
	public  R reduceEntries(BiFunction, ? extends R> func, R initial) {
		int n = children.length;
		R result = initial;
		for (int i = 0; i < n; i++) {
			result = children[i].getValue().reduceEntries(func, result);
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(ACell a) {
		if (!(a instanceof MapTree)) return false;
		return equals((MapTree) a);
	}

	boolean equals(MapTree b) {
		if (b==null) return false;
		if (this == b) return true;
		long n = count;
		if (n != b.count) return false;
		if (mask != b.mask) return false;
		if (shift != b.shift) return false;

		// Fall back to comparing hashes. Probably most efficient in general.
		if (getHash().equals(b.getHash())) return true;
		return false;
	}

	@Override
	public AHashMap mapEntries(Function, MapEntry> func) {
		int n = children.length;
		if (n == 0) return this;
		Ref>[] newChildren = children;
		for (int i = 0; i < n; i++) {
			AHashMap child = children[i].getValue();
			AHashMap newChild = child.mapEntries(func);
			if (child != newChild) {
				if (children == newChildren) {
					newChildren = children.clone();
				}
				newChildren[i] = newChild.getRef();
			}
		}
		if (newChildren == children) return this;

		// Note: creation should remove any empty children. Need to recompute count
		// since
		// entries may have been removed.
		return create(newChildren, shift, mask, computeCount(newChildren));
	}

	@Override
	public void validate() throws InvalidDataException {
		super.validate();
		
		// Perform full tree validation with prefix if this is a top level element
		if (isCVMValue()) validateWithPrefix("");
	}

	@Override
	protected void validateWithPrefix(String prefix) throws InvalidDataException {
		if (mask == 0) throw new InvalidDataException("TreeMap must have children!", this);
		if (shift != prefix.length()) {
			throw new InvalidDataException("Invalid prefix [" + prefix + "] for TreeMap with shift=" + shift, this);
		}
		int bsize = children.length;

		long childCount=0;;
		for (int i = 0; i < bsize; i++) {
			if (children[i] == null)
				throw new InvalidDataException("Null child ref at " + prefix + Utils.toHexChar(digitForIndex(i, mask)),
						this);
			ACell o = children[i].getValue();
			if (!(o instanceof AHashMap)) {
				throw new InvalidDataException(
						"Expected map child at " + prefix + Utils.toHexChar(digitForIndex(i, mask)), this);
			}
			@SuppressWarnings("unchecked")
			AHashMap child = (AHashMap) o;
			if (child.isEmpty())
				throw new InvalidDataException("Empty child at " + prefix + Utils.toHexChar(digitForIndex(i, mask)),
						this);
			int d = digitForIndex(i, mask);
			child.validateWithPrefix(prefix + Utils.toHexChar(d));
			
			childCount += child.count();
		}
		
		if (count != childCount) {
			throw new InvalidDataException("Bad child count, expected " + count + " but children had: " + childCount, this);
		}
	}

	private boolean isValidStructure() {
		if (count <= MapLeaf.MAX_ENTRIES) return false;
		if (children.length != Integer.bitCount(mask & 0xFFFF)) return false;
		for (int i = 0; i < children.length; i++) {
			if (children[i] == null) return false;
		}
		return true;
	}

	@Override
	public void validateCell() throws InvalidDataException {
		if (!isValidStructure()) throw new InvalidDataException("Bad structure", this);
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean containsAllKeys(AHashMap map) {
		if (map instanceof MapTree) {
			return containsAllKeys((MapTree)map);
		}
		// must be a MapLeaf
		long n=map.count;
		for (long i=0; i me=map.entryAt(i);
			if (!this.containsKeyRef((Ref) me.getKeyRef())) return false;
		}
		return true;
	}
	
	protected boolean containsAllKeys(MapTree map) {
		// fist check this mask contains all of target mask
		if ((this.mask|map.mask)!=this.mask) return false;
		
		for (int i=0; i> child=this.childForDigit(i);
			if (child==null) continue;
			
			Ref> mchild=map.childForDigit(i);
			if (mchild==null) continue;
			
			if (!(child.getValue().containsAllKeys(mchild.getValue()))) return false; 
		}
		return true;
	}

	@Override
	public byte getTag() {
		return Tag.MAP;
	}

	@Override
	public AHashMap toCanonical() {
		if (count > MapLeaf.MAX_ENTRIES) return this;
		// shouldn't be possible?
		throw new TODOException();
	}

	@Override
	public AHashMap slice(long start, long end) {
		if ((start<0)||(end>count)||(end>[] newChildren=children; 
		for (int i=0; i child=children[i].getValue();
			long csize=child.size();
			long cend=pos+csize;
			if (cend<=start) {
				// haven't reached range yet
				istart++; iend++;
				pos+=csize;
				continue;
			} else if (end<=cend) {
				// we reached the end of the range at this child
				if (i==istart) return child.slice(start-pos,end-pos); // slice within single child! important optimisation
				if (end<=pos) break; //nothing to include from this child, we are done
			} 
			// defensive copy if needed
			if (children==newChildren) newChildren=children.clone();
			AHashMap newChild=child.slice(Math.max(0, start-pos), Math.min(csize, end-pos));
			newChildren[i]=newChild.getRef();
			
			// mark mash and and extent index range for new child
			m|=(short)(1<(newChildren,shift,m,end-start);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private AHashMap smallSlice(long start, long end) {
		int n=(int)(end-start);
		MapEntry[] items=new MapEntry[n];
		for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy