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

com.itranswarp.eth.smt.NibbleString Maven / Gradle / Ivy

package com.itranswarp.eth.smt;

import java.util.Arrays;
import java.util.Objects;

/**
 * Immutable half-byte-string. Each element is in range of 0 ~ 0xf.
 */
public final class NibbleString {

    private static final int[] EMPTY_ARRAY = new int[0];
    private static final String HEX_STRING = "0123456789abcdef";
    private static final char[] HEX_CHARS = HEX_STRING.toCharArray();

    private final int[] value;
    private final int offset;
    private final int count;
    private int hash;

    /**
     * Empty nibble string.
     */
    public static final NibbleString EMPTY = new NibbleString(EMPTY_ARRAY, 0, 0);

    /**
     * Build by hex string.
     * 
     * 
     * "a1b2" -> NibbleString { 0xa, 1, 0xb, 2 }
     * 
     * 
     * @param hexString The hex string.
     */
    public NibbleString(String hexString) {
        if (hexString.isEmpty()) {
            this.value = EMPTY_ARRAY;
            this.offset = 0;
            this.count = 0;
        } else {
            int len = hexString.length();
            int[] val = new int[len];
            for (int i = 0; i < len; i++) {
                char ch = hexString.charAt(i);
                if (ch >= '0' && ch <= '9') {
                    val[i] = ch - '0';
                } else if (ch >= 'a' && ch <= 'f') {
                    val[i] = ch + 10 - 'a';
                } else {
                    throw new IllegalArgumentException("Invalid hex string char: " + ch);
                }
            }
            this.value = val;
            this.offset = 0;
            this.count = len;
        }
    }

    /**
     * Build by bytes array.
     * 
     * 
     * byte[] {0x2f, 0x3c}" -> NibbleString { 2, 0xf, 3, 0xc }
     * 
     * 
     * @param original The byte array.
     */
    public NibbleString(byte[] original) {
        if (original.length == 0) {
            this.value = EMPTY_ARRAY;
            this.offset = 0;
            this.count = 0;
        } else {
            int len = original.length;
            int[] val = new int[len << 1];
            for (int i = 0; i < len; i++) {
                int offset = i << 1;
                byte b = original[i];
                val[offset] = (b & 0xf0) >>> 4;
                val[offset + 1] = b & 0x0f;
            }
            this.value = val;
            this.offset = 0;
            this.count = val.length;
        }
    }

    /**
     * Append a nibble.
     * 
     * @param next Next nibble path.
     * @return Nibble string.
     */
    public NibbleString join(int next) {
        if (next < 0 || next > 15) {
            throw new IllegalArgumentException("Next nibble must between 0 ~ 15.");
        }
        int[] copy = new int[this.count + 1];
        System.arraycopy(this.value, this.offset, copy, 0, this.count);
        copy[this.count] = next;
        return new NibbleString(copy, 0, this.count + 1);
    }

    private NibbleString(int[] original, int offset, int count) {
        this.value = original;
        this.offset = offset;
        this.count = count;
    }

    /**
     * Substring from beginIndex.
     * 
     * @param beginIndex Begin index.
     * @return Nibble string.
     */
    public NibbleString substring(int beginIndex) {
        return substring(beginIndex, this.count);
    }

    /**
     * Substring from beginIndex and endIndex (exclusive).
     * 
     * @param beginIndex Begin index.
     * @param endIndex   End index.
     * @return Nibble string.
     */
    public NibbleString substring(int beginIndex, int endIndex) {
        if (beginIndex == 0 && endIndex == this.count) {
            return this;
        }
        Objects.checkFromToIndex(beginIndex, endIndex, this.count);
        return new NibbleString(this.value, this.offset + beginIndex, endIndex - beginIndex);
    }

    /**
     * Length of this nibble string.
     * 
     * @return Length of nibble string.
     */
    public int length() {
        return this.count;
    }

    /**
     * Nibble value at index.
     * 
     * @param index Index.
     * @return Nibble value.
     */
    public int valueAt(int index) {
        Objects.checkIndex(index, this.count);
        return this.value[this.offset + index];
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof NibbleString) {
            NibbleString bs = (NibbleString) o;
            return Arrays.equals(this.value, this.offset, this.offset + this.count, bs.value, bs.offset, bs.offset + bs.count);
        }
        return false;
    }

    @Override
    public int hashCode() {
        int h = this.hash;
        if (h == 0 && this.count > 0) {
            h = 1;
            int off = this.offset;
            for (int i = 0; i < this.count; i++) {
                h = 31 * h + this.value[off++];
            }
            this.hash = h;
        }
        return h;
    }

    @Override
    public String toString() {
        if (this.count == 0) {
            return "";
        }
        var sb = new StringBuilder(this.count);
        int off = this.offset;
        for (int i = 0; i < this.count; i++) {
            sb.append(HEX_CHARS[this.value[off++]]);
        }
        return sb.toString();
    }

    /**
     * Is empty nibble string.
     * 
     * @return True if empty.
     */
    public boolean isEmpty() {
        return this.count == 0;
    }

    /**
     * Tests if this string starts with the specified prefix.
     *
     * @param prefix the prefix.
     * @return true if the character sequence represented by the
     *         argument is a prefix of the character sequence represented by this
     *         string; false otherwise.
     */
    public boolean startsWith(NibbleString prefix) {
        return startsWith(prefix, 0);
    }

    /**
     * Tests if the substring of this string beginning at the specified index starts
     * with the specified prefix.
     * 
     * @param prefix  prefix the prefix.
     * @param toffset toffset where to begin looking in this string.
     * @return true if the character sequence represented by the
     *         argument is a prefix of the substring of this object starting at
     *         index toffset; false otherwise.
     */
    public boolean startsWith(NibbleString prefix, int toffset) {
        int ta[] = this.value;
        int to = this.offset + toffset;
        int pa[] = prefix.value;
        int po = prefix.offset;
        int pc = prefix.count;
        // Note: toffset might be near -1>>>1.
        if ((toffset < 0) || (toffset > count - pc)) {
            return false;
        }
        while (--pc >= 0) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get shared prefix as many as possible. Example: "1a2b3c" and "1a2f4d" share
     * "1a2".
     * 
     * @param s1 A nibble string.
     * @param s2 Another nibble string.
     * @return Share prefix.
     */
    public static NibbleString sharedPrefix(NibbleString s1, NibbleString s2) {
        int max = Math.min(s1.length(), s2.length());
        if (max == 0) {
            return EMPTY;
        }
        int n = 0;
        while (n < max) {
            if (s1.valueAt(n) != s2.valueAt(n)) {
                return s1.substring(0, n);
            }
            n++;
        }
        return s1.substring(0, max);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy