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

com.github.jtendermint.merkletree.iavl.Node Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2016 - 2018
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.github.jtendermint.merkletree.iavl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

import com.github.jtendermint.merkletree.iavl.IterateFunct.Loop;
import com.github.jtendermint.merkletree.HashWithCount;

public class Node> {

    private K value;
    private int height;
    private int size;

    private byte[] hash;
    private byte[] leftChildHash;
    private byte[] rightChildHash;

    private Node leftChildNode;
    private Node rightChildNode;

    private Hashing hashFunction;

    public Node init(K value) {
        return init(value, 0, 1, null, null, null, null);
    }
    public Node init(K key, Node leftNode, Node rightNode) {
        return init(key, 1, 2, null, leftNode, null, rightNode);
    }

    public Node init(K value, int height, int size, byte[] leftHash, Node leftNode, byte[] rightHash, Node rightNode) {
        this.value = value;
        this.height = height;
        this.size = size;
        this.hash = null;
        this.leftChildHash = leftHash == null ? null : Arrays.copyOf(leftHash, leftHash.length);
        this.leftChildNode = leftNode;
        this.rightChildHash = rightHash == null ? null : Arrays.copyOf(rightHash, rightHash.length);
        this.rightChildNode = rightNode;
        return this;
    }

    public K getValue() {
        return value;
    }

    public Node setHashFunction(Hashing hash) {
        this.hashFunction = hash;
        return this;
    }

    public int getHeight() {
        return height;
    }

    public int getSize() {
        return size;
    }

    public boolean contains(K value) {
        return get(value) != null;
    }

    public K get(K entry) {
        if (entry != null && entry.equals(value)) {
            return value;
        } else if (this.height == 0) {
            return null;
        } else {
            if (entry.compareTo(value) < 0) {
                return this.leftChildNode.get(entry);
            } else {
                return this.rightChildNode.get(entry);
            }
        }
    }

    public KeyIndex get(int index) {
        if (this.height == 0) {
            if (index == 0) {
                return new KeyIndex(value, true, 0);
            } else {
                throw new RuntimeException("Asked for index > 0 with a height of 0");
            }
        } else {
            if (index < leftChildNode.size) {
                return leftChildNode.get(index);
            } else {
                return rightChildNode.get(index - leftChildNode.size);
            }
        }
    }

    public AddResult add(K value) {
        int compareResult = value.compareTo(this.value);
        if (height == 0) {
            if (compareResult < 0) {
                Node newNode = new Node<>();
                newNode.init(this.value, this.newNode().init(value).setHashFunction(hashFunction), this).setHashFunction(hashFunction);
                return new AddResult(newNode, false);
            } else if (compareResult == 0) {
                return new AddResult(this.newNode().init(value).setHashFunction(hashFunction), true);
            } else {
                Node newNode = new Node<>();
                newNode.init(value, this, this.newNode().init(value).setHashFunction(hashFunction)).setHashFunction(hashFunction);
                return new AddResult(newNode, false);
            }
        } else {
            Node newNode = this.createCopy();
            AddResult newNodeResult;
            if (value.compareTo(newNode.value) < 0) {
                newNodeResult = newNode.leftChildNode.add(value);
                newNode.leftChildNode = newNodeResult.getNode(); // newNodeResult.getNode();
                newNode.leftChildHash = null;
            } else {
                newNodeResult = newNode.rightChildNode.add(value);
                newNode.rightChildNode = newNodeResult.getNode();
                newNode.rightChildHash = null;
            }

            if (newNodeResult.wasUpdated()) {
                return new AddResult(newNode, true);
            } else {
                newNode.updateHeightAndSize();
                return new AddResult(newNode.balance(), false);
            }
        }
    }
    public boolean remove(K entry) {
        return false;
    }

    private Node balance() {
        int balance = this.getBalance();
        if (balance > 1) {
            if (this.leftChildNode.getBalance() >= 0) {
                // Left Left Case
                return this.rotateRight();
            } else {
                // Left Right Case
                Node newNode = this.createCopy();
                newNode.leftChildHash = null;
                newNode.leftChildNode = newNode.leftChildNode.rotateLeft();
                return newNode.rotateRight();
            }
        }
        if (balance < -1) {
            if (this.rightChildNode.getBalance() <= 0) {
                // Right Right Case
                return this.rotateLeft();
            } else {
                // Right Left Case
                Node newNode = this.createCopy();
                newNode.rightChildHash = null;
                newNode.rightChildNode = newNode.rightChildNode.rotateRight();
                return newNode.rotateLeft();
            }
        }
        // no changes - balanced
        return this;
    }

    private Node rotateLeft() {
        Node newNode = this.createCopy();
        Node rightCopy = newNode.rightChildNode.createCopy();

        newNode.rightChildHash = rightCopy.leftChildHash;
        newNode.rightChildNode = rightCopy.leftChildNode;
        rightCopy.leftChildNode = newNode;

        newNode.updateHeightAndSize();
        rightCopy.updateHeightAndSize();
        return rightCopy;
    }

    private Node rotateRight() {
        Node newNode = this.createCopy();
        Node leftCopy = newNode.leftChildNode.createCopy();

        newNode.leftChildHash = leftCopy.rightChildHash;
        newNode.leftChildNode = leftCopy.rightChildNode;
        leftCopy.rightChildHash = null;
        leftCopy.rightChildNode = newNode;

        newNode.updateHeightAndSize();
        leftCopy.updateHeightAndSize();
        return leftCopy;
    }

    private int getBalance() {
        return leftChildNode.height - rightChildNode.height;
    }

    private void updateHeightAndSize() {
        this.height = Math.max(leftChildNode.getHeight(), rightChildNode.getHeight()) + 1;
        this.size = leftChildNode.getSize() + rightChildNode.getSize();
    }

    public Node createCopy() {
        if (this.height == 0) {
            throw new RuntimeException("Cannot copy Value-Nodes");
        } else {
            return newNode().init(value, this.height, this.size, this.leftChildHash, this.leftChildNode, this.rightChildHash,
                    this.rightChildNode).setHashFunction(hashFunction);
        }
    }

    public String toPrettyString() {
        if (this.height == 0) {
            return String.valueOf(value);
        } else {
            return "(" + this.leftChildNode.toPrettyString() + " " + this.rightChildNode.toPrettyString() + ")";
        }
    }

    public boolean isLeafNode() {
        return height == 0;
    }

    public Loop iterateNodes(IterateFunct func) {
        Loop stop = func.currentNode(this);
        if (stop == Loop.STOP) {
            return stop;
        }
        if (this.height > 0) {
            stop = this.leftChildNode.iterateNodes(func);
            if (stop == Loop.STOP) {
                return Loop.STOP;
            }
            stop = this.rightChildNode.iterateNodes(func);
            if (stop == Loop.STOP) {
                return Loop.STOP;
            }
        }
        return Loop.CONTINUE;
    }

    protected Node newNode() {
        return new Node();
    }

    public HashWithCount getHashWithCount() {
        if (this.hash != null) {
            return new HashWithCount(this.hash, 0);
        }
        try (ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream()) {
            int hashCount = this.writeHashBytes(byteOutStream);
            this.hash = hashFunction.hashBytes(byteOutStream.toByteArray());
            return new HashWithCount(this.hash, hashCount + 1);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private int writeHashBytes(ByteArrayOutputStream bos) throws IOException {
        int hashCount = 0;
        // ByteUtil.writeWithVarint(BigInteger.valueOf(size).toByteArray(),
        // bos);
        if (this.height == 0) {
            byte[] hashBytes = hashFunction.hashBytes(this.value);
            bos.write(hashBytes);
        } else {
            if (this.leftChildNode != null) {
                HashWithCount leftHashCount = this.leftChildNode.getHashWithCount();
                this.leftChildHash = Objects.requireNonNull(leftHashCount.hash, "this.leftHash was null in writeHashBytes");
                hashCount += leftHashCount.count;
            }
            if (this.rightChildNode != null) {
                HashWithCount rightHashCount = this.rightChildNode.getHashWithCount();
                this.rightChildHash = Objects.requireNonNull(rightHashCount.hash, "this.rightHash was null in writeHashBytes");
                hashCount += rightHashCount.count;
            }
            byte[] hashBytesRight = hashFunction.hashBytes(this.rightChildHash);
            byte[] hashBytesLeft = hashFunction.hashBytes(this.leftChildHash);
            bos.write(hashBytesLeft);
            bos.write(hashBytesRight);

        }
        return hashCount;
    }

    public byte[] save() {
        if (hash == null) {
            hash = getHashWithCount().hash;
        }

        if (leftChildNode != null) {
            leftChildHash = leftChildNode.save();
        }
        if (rightChildNode != null) {
            rightChildHash = rightChildNode.save();
        }

        return Arrays.copyOf(hash, hash.length);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy