convex.core.data.MapTree 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.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 super K, ? super V> 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 super R, ? super V, ? extends R> 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 super R, MapEntry, ? 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