package com.ripple.core.types.shamap;
import com.ripple.core.coretypes.hash.Hash256;
import com.ripple.core.coretypes.hash.prefixes.HashPrefix;
import com.ripple.core.coretypes.hash.prefixes.Prefix;
import com.ripple.core.serialized.BytesSink;
import java.util.Iterator;
public class ShaMapInner extends ShaMapNode implements Iterable<ShaMapNode> {
public int depth;
int slotBits = 0;
int version = 0;
boolean doCoW;
protected ShaMapNode[] branches = new ShaMapNode[16];
public ShaMapInner(int depth) {
this(false, depth, 0);
}
public ShaMapInner(boolean isCopy, int depth, int version) {
this.doCoW = isCopy;
this.depth = depth;
this.version = version;
}
protected ShaMapInner copy(int version) {
ShaMapInner copy = makeInnerOfSameClass(depth);
System.arraycopy(branches, 0, copy.branches, 0, branches.length);
copy.slotBits = slotBits;
copy.hash = hash;
copy.version = version;
doCoW = true;
return copy;
}
protected ShaMapInner makeInnerOfSameClass(int depth) {
return new ShaMapInner(true, depth, version);
}
protected ShaMapInner makeInnerChild() {
int childDepth = depth + 1;
if (childDepth >= 64) throw new AssertionError();
return new ShaMapInner(doCoW, childDepth, version);
}
// Descend into the tree, find the leaf matching this index
// and if the tree has it.
protected void setLeaf(ShaMapLeaf leaf) {
if (leaf.version == -1) {
leaf.version = version;
}
setBranch(leaf.index, leaf);
}
private void removeBranch(Hash256 index) {
removeBranch(selectBranch(index));
}
public void walkLeaves(LeafWalker leafWalker) {
for (ShaMapNode branch : branches) {
if (branch != null) {
if (branch.isInner()) {
branch.asInner().walkLeaves(leafWalker);
} else if (branch.isLeaf()) {
leafWalker.onLeaf(branch.asLeaf());
}
}
}
}
public void walkTree(TreeWalker treeWalker) {
treeWalker.onInner(this);
for (ShaMapNode branch : branches) {
if (branch != null) {
if (branch.isLeaf()) {
ShaMapLeaf ln = branch.asLeaf();
treeWalker.onLeaf(ln);
} else if (branch.isInner()) {
ShaMapInner childInner = branch.asInner();
childInner.walkTree(treeWalker);
}
}
}
}
public void walkHashedTree(HashedTreeWalker walker) {
walker.onInner(hash(), this);
for (ShaMapNode branch : branches) {
if (branch != null) {
if (branch.isLeaf()) {
ShaMapLeaf ln = branch.asLeaf();
walker.onLeaf(branch.hash(), ln);
} else if (branch.isInner()) {
ShaMapInner childInner = branch.asInner();
childInner.walkHashedTree(walker);
}
}
}
}
/**
* @return the `only child` leaf or null if other children
*/
public ShaMapLeaf onlyChildLeaf() {
ShaMapLeaf leaf = null;
int leaves = 0;
for (ShaMapNode branch : branches) {
if (branch != null) {
if (branch.isInner()) {
leaf = null;
break;
} else if (++leaves == 1) {
leaf = branch.asLeaf();
} else {
leaf = null;
break;
}
}
}
return leaf;
}
public boolean removeLeaf(Hash256 index) {
PathToIndex path = pathToIndex(index);
if (path.hasMatchedLeaf()) {
ShaMapInner top = path.dirtyOrCopyInners();
top.removeBranch(index);
path.collapseOnlyLeafChildInners();
return true;
} else {
return false;
}
}
public ShaMapItem getItem(Hash256 index) {
ShaMapLeaf leaf = getLeaf(index);
@SuppressWarnings("unchecked")
ShaMapItem shaMapItem = leaf == null ? null : leaf.item;
return shaMapItem;
}
public boolean addItem(Hash256 index, ShaMapItem item) {
return addLeaf(new ShaMapLeaf(index, item));
}
public boolean updateItem(Hash256 index, ShaMapItem item) {
return updateLeaf(new ShaMapLeaf(index, item));
}
public boolean hasLeaf(Hash256 index) {
return pathToIndex(index).hasMatchedLeaf();
}
public ShaMapLeaf getLeaf(Hash256 index) {
PathToIndex stack = pathToIndex(index);
if (stack.hasMatchedLeaf()) {
return stack.leaf;
} else {
return null;
}
}
public boolean addLeaf(ShaMapLeaf leaf) {
PathToIndex stack = pathToIndex(leaf.index);
if (stack.hasMatchedLeaf()) {
return false;
} else {
ShaMapInner top = stack.dirtyOrCopyInners();
top.addLeafToTerminalInner(leaf);
return true;
}
}
public boolean updateLeaf(ShaMapLeaf leaf) {
PathToIndex stack = pathToIndex(leaf.index);
if (stack.hasMatchedLeaf()) {
ShaMapInner top = stack.dirtyOrCopyInners();
// Why not update in place? Because of structural sharing
top.setLeaf(leaf);
return true;
} else {
return false;
}
}
public PathToIndex pathToIndex(Hash256 index) {
return new PathToIndex(this, index);
}
/**
* This should only be called on the deepest inners, as it
* does not do any dirtying.
* @param leaf to add to inner
*/
void addLeafToTerminalInner(ShaMapLeaf leaf) {
ShaMapNode branch = getBranch(leaf.index);
if (branch == null) {
setLeaf(leaf);
} else if (branch.isInner()) {
// This should never be called
throw new AssertionError();
} else if (branch.isLeaf()) {
ShaMapInner inner = makeInnerChild();
setBranch(leaf.index, inner);
inner.addLeafToTerminalInner(leaf);
inner.addLeafToTerminalInner(branch.asLeaf());
}
}
protected void setBranch(Hash256 index, ShaMapNode node) {
setBranch(selectBranch(index), node);
}
protected ShaMapNode getBranch(Hash256 index) {
return getBranch(index.nibblet(depth));
}
public ShaMapNode getBranch(int i) {
return branches[i];
}
public ShaMapNode branch(int i) {
return branches[i];
}
protected int selectBranch(Hash256 index) {
return index.nibblet(depth);
}
public boolean hasLeaf(int i) {
return branches[i].isLeaf();
}
public boolean hasInner(int i) {
return branches[i].isInner();
}
public boolean hasNone(int i) {return branches[i] == null;}
private void setBranch(int slot, ShaMapNode node) {
slotBits = slotBits | (1 << slot);
branches[slot] = node;
invalidate();
}
private void removeBranch(int slot) {
branches[slot] = null;
slotBits = slotBits & ~(1 << slot);
}
public boolean empty() {
return slotBits == 0;
}
@Override public boolean isLeaf() { return false; }
@Override public boolean isInner() { return true; }
@Override
Prefix hashPrefix() {
return HashPrefix.innerNode;
}
@Override
public void toBytesSink(BytesSink sink) {
for (ShaMapNode branch : branches) {
if (branch != null) {
branch.hash().toBytesSink(sink);
} else {
Hash256.ZERO_256.toBytesSink(sink);
}
}
}
@Override
public Hash256 hash() {
if (empty()) {
// empty inners have a hash of all ZERO
// it's only valid for a root node to be empty
// any other inner node, must contain at least a
// single leaf
assert depth == 0;
return Hash256.ZERO_256;
} else {
// hash the hashPrefix() and toBytesSink
return super.hash();
}
}
public ShaMapLeaf getLeafForUpdating(Hash256 leaf) {
PathToIndex path = pathToIndex(leaf);
if (path.hasMatchedLeaf()) {
return path.invalidatedPossiblyCopiedLeafForUpdating();
}
return null;
}
@Override
public Iterator<ShaMapNode> iterator() {
return new Iterator<ShaMapNode>() {
int ix = 0;
@Override
public boolean hasNext() {
return ix != 16;
}
@Override
public ShaMapNode next() {
return branch(ix++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}