de.uniba.wiai.lspi.chord.data.ID.java Source code

Java tutorial

Introduction

Here is the source code for de.uniba.wiai.lspi.chord.data.ID.java

Source

/***************************************************************************
 *                                                                         *
 *                                 ID.java                                 *
 *                            -------------------                          *
 *   date                 : 16.08.2004                                     *
 *   copyright            : (C) 2004-2008 Distributed and                  *
 *                              Mobile Systems Group                       *
 *                              Lehrstuhl fuer Praktische Informatik       *
 *                              Universitaet Bamberg                       *
 *                              http://www.uni-bamberg.de/pi/              *
 *   email                : sven.kaffille@uni-bamberg.de                   *
 *                          karsten.loesing@uni-bamberg.de                 *
 *                                                                         *
 *                                                                         *
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   A copy of the license can be found in the license.txt file supplied   *
 *   with this software or at: http://www.gnu.org/copyleft/gpl.html        *
 *                                                                         *
 ***************************************************************************/
package de.uniba.wiai.lspi.chord.data;

import java.io.Serializable;
import java.util.BitSet;
import java.util.Optional;

import org.apache.commons.codec.digest.DigestUtils;

import com.chord4js.ProviderId;
import com.chord4js.ServiceId;
import com.chord4js.Unit;

/**
 * Identifier for nodes and user-defined objects. New instances of this class
 * are created either when a node joins the network, or by the local node
 * inserting a user-defined object.
 * 
 * Once created, an ID instance is unmodifiable.
 * 
 * IDs of same length can be compared as this class implements
 * java.lang.Comparable. IDs of different length cannot be compared.
 * 
 * @author Sven Kaffille, Karsten Loesing
 * @version 1.0.5
 */
public final class ID implements Comparable<ID>, Serializable, Cloneable {
    /**
     * 
     */
    private static final long serialVersionUID = 6860626236168125168L;

    // Expected total network size. This is important for calc'ing the ideal length
    // for the provider bits.
    public static final int kNetworkSizeExpected = 100000;
    // Maximum number of providers for a single semantic ident
    public static final int kProviderLimit = 1024;
    public static final int kSemanticPartBitLen = 6;
    public static final int kSemanticTotalBitLen = kSemanticPartBitLen * ServiceId.kPartsSemantic;
    public static final int kProviderBitLen = ProviderMinimumBitLen();
    public static final int kTotalBitLen = kSemanticTotalBitLen + kProviderBitLen;

    private static final ID idMin; // All 0 ID
    private static final ID idMax; // All 1 ID

    // From section 4.2 in paper:
    //   providerBitLen >= log2((numProviderMax - 1) / nodeNum * 2^semanticBitLen)
    // Which rewrites to:
    //   providerBitLen >= log2(numProviderMax - 1) + log2(2^semanticBitLen / nodeNum)
    //   providerBitLen >= log2(numProviderMax - 1) + semanticBitLen - log2(nodeNum)
    private static int ProviderMinimumBitLen() {
        final double lenMin = log2(kProviderLimit - 1) - log2(kNetworkSizeExpected) + (double) kSemanticTotalBitLen;
        return (int) Math.ceil(lenMin);
    }

    // Math has no log2.
    private static double log2(double x) {
        return Math.log10(x) / Math.log10(2);
    }

    static {
        idMin = new ID();
        idMax = new ID();
        idMax.id.set(0, kTotalBitLen);
    }

    /**
     * The bits representing the id. Big endian. (provider bits are low)
     */
    public final BitSet id;

    private static BitSet hashString(final String s) {
        return BitSet.valueOf(DigestUtils.sha1(s));
    }

    // up-sizing results in 0 being padded at the end
    // \todo ensure we reduce using the highest entropy bits
    private static BitSet bitsetResize(final BitSet a, final int numBits) {
        assert (numBits > 0);
        final BitSet b = new BitSet(numBits);
        for (int i = 0; i < Math.min(a.size(), numBits); ++i)
            b.set(i, a.get(i));

        return b;
    }

    private static void partHashSet(final BitSet dst, final int dstOffset, final BitSet src, final int srcLen) {
        assert (dstOffset + srcLen <= dst.size());
        for (int i = 0; i < Math.min(src.size(), srcLen); ++i)
            dst.set(dstOffset + i, src.get(i));
    }

    // This is fucking absurd Java, get your crap together.
    @FunctionalInterface
    interface Fn0<_R> {
        public _R apply();
    }

    @FunctionalInterface
    interface Fn1<A, _R> {
        public _R apply(A a);
    }

    @FunctionalInterface
    interface Fn2<A, B, _R> {
        public _R apply(A a, B b);
    }

    @FunctionalInterface
    interface Fn3<A, B, C, _R> {
        public _R apply(A a, B b, C c);
    }

    private static BitSet hashServiceId(ServiceId svcId) {
        final BitSet bs = new BitSet(kTotalBitLen);
        final Fn3<Integer, Integer, String, Unit> hashInto = (Integer offset, Integer bitLen, String s) -> {
            if (s != null) {
                partHashSet(bs, offset, hashString(s), bitLen);
            }

            return Unit.U;
        };

        // hash semantic 
        for (int i = 0; i < ServiceId.kPartsSemantic; ++i)
            hashInto.apply(i * kSemanticPartBitLen, kSemanticPartBitLen, svcId.parts[i]);

        // hash provider
        hashInto.apply(kSemanticTotalBitLen, kProviderBitLen, svcId.parts[ServiceId.kPartsSemantic]);

        return bs;
    }

    private static BitSet bitsetArithPwrOf2(final BitSet _bs, final int pwrOf2, final boolean addition) {
        // The bitset is defined as big-endian unsigned, i.e. (idx 0 is highest bit)
        final BitSet bs = (BitSet) _bs.clone();
        assert (pwrOf2 < kTotalBitLen);
        for (int i = kTotalBitLen - 1 - pwrOf2; i >= 0; --i) {
            bs.flip(i);
            // add/sub for unsigned binary is the same, except we bail if (set == addition) after flip
            if (bs.get(i) == addition)
                break;
        }

        return bs;
    }

    protected static BitSet bitsetSubPwrOf2(final BitSet _bs, final int pwrOf2) {
        return bitsetArithPwrOf2(_bs, pwrOf2, false);
    }

    protected static BitSet bitsetAddPwrOf2(final BitSet _bs, final int pwrOf2) {
        return bitsetArithPwrOf2(_bs, pwrOf2, true);
    }

    public static class IdSpan {
        public static final IdSpan kEmpty = new IdSpan(Optional.empty());
        public static final IdSpan kAll = Inclusive(idMin, idMax);

        public static IdSpan Inclusive(final ID bgn, final ID end) {
            return new IdSpan(Optional.of(new SpanInclusive(bgn, end)));
        }

        public boolean empty() {
            return !span.isPresent();
        }

        @Override
        public String toString() {
            return empty() ? "[<empty>]" : "[" + bgn() + ", " + end() + "]";
        }

        public boolean contains(final ID x) {
            return span.map((SpanInclusive s) -> {
                final Fn2<ID, ID, Boolean> ltEq = (ID a, ID b) -> a.compareTo(b) <= 0;
                final Fn2<ID, ID, Boolean> inSpan = (ID a, ID b) -> ltEq.apply(a, x) && ltEq.apply(x, b);
                if (ltEq.apply(s.bgn, s.end)) // non-wrapping
                    return inSpan.apply(s.bgn, s.end);

                // wraps around, check both [bgn, max] and [min, end]
                return inSpan.apply(s.bgn, idMax) || inSpan.apply(idMin, s.end);
            }).orElse(false);
        }

        public boolean containsExcl(ID x) {
            return span.map((SpanInclusive s) -> {
                return (!(s.bgn.equals(x) || s.end.equals(x))) && contains(x);
            }).orElse(false);
        }

        public ID bgn() {
            return span.map((SpanInclusive s) -> s.bgn).orElse(null);
        }

        public ID end() {
            return span.map((SpanInclusive s) -> s.end).orElse(null);
        }

        public IdSpan subsetMin(final ID newMin) {
            return span.map((SpanInclusive s) -> {
                return contains(newMin) ? Inclusive(newMin, s.end) : kEmpty;
            }).orElse(kEmpty);
        }

        private static class SpanInclusive {
            public final ID bgn, end;

            SpanInclusive(ID _bgn, ID _end) {
                bgn = _bgn;
                end = _end;
            }
        }

        private final Optional<SpanInclusive> span;

        private IdSpan(Optional<SpanInclusive> s) {
            span = s;
        }
    }

    public static IdSpan ServiceId(ServiceId svcId) {
        final ID bgn = new ID(hashServiceId(svcId));

        final BitSet bsEndIncl = (BitSet) bgn.id.clone();
        final Fn2<Integer, Integer, Unit> endFillBits = (Integer offset, Integer len) -> {
            assert (offset + len <= kTotalBitLen);
            bsEndIncl.set(offset, offset + len);
            return Unit.U;
        };

        for (int i = svcId.partsGivenCount(); i < ServiceId.kPartsSemantic; ++i)
            endFillBits.apply(i * kSemanticPartBitLen, kSemanticPartBitLen);

        if (svcId.getProviderPart() == null)
            endFillBits.apply(kSemanticTotalBitLen, kProviderBitLen);

        final ID end = new ID(bsEndIncl);
        return IdSpan.Inclusive(bgn, end);
    }

    public static ID NodeId(final byte[] blob) {
        return new ID(bitsetResize(BitSet.valueOf(blob), kTotalBitLen));
    }

    public ID(ProviderId x) {
        id = hashServiceId(x);
    }

    private ID() {
        id = new BitSet(kTotalBitLen);
    }

    private ID(BitSet x) {
        id = x;
    }

    @Override
    public ID clone() {
        return new ID((BitSet) id.clone());
    }

    public final String toString() {
        return toHexString();
    }

    /**
     * Returns a string of the hexadecimal representation of the first
     * <code>n</code> bytes of this ID, including leading zeros.
     * 
     * @param numberOfBytes
     * 
     * @return Hex string of ID
     */
    public final String toHexString(int numberOfBytes) {

        // number of displayed bytes must be in interval [1, ceil(kTotalBitLen / 8) )
        final int kNumByte = (kTotalBitLen + 7) / 8; // BitSet size cannot be specified exactly.
        final byte[] ary = new byte[kNumByte];
        final int aryBits = ary.length * 8;
        final int aryBitPad = aryBits - kTotalBitLen;
        for (int i = aryBitPad; i < aryBits; ++i) {
            final int bitIdx = i - aryBitPad;
            // note that ID is big-endian encoded
            ary[i / 8] |= (id.get(bitIdx) ? 1 : 0) << (7 - (i % 8));
        }
        int displayBytes = Math.max(1, Math.min(numberOfBytes, ary.length));

        StringBuilder result = new StringBuilder();
        for (int i = 0; i < displayBytes; i++) {

            String block = Integer.toHexString(ary[i] & 0xff).toUpperCase();

            // add leading zero to block, if necessary
            if (block.length() < 2) {
                block = "0" + block;
            }

            result.append(block + " ");
        }
        return result.toString();
    }

    /**
     * Returns a string of the hexadecimal representation of this ID, including
     * leading zeros.
     * 
     * @return Hex string of ID
     */
    public final String toHexString() {
        return this.toHexString(Integer.MAX_VALUE);
    }

    /**
     * Returns length of this ID measured in bits. ID length is determined by
     * the length of the stored byte[] array, i.e. leading zeros have to be
     * stored in the array.
     * 
     * @return Length of this ID measured in bits.
     */
    public final int getLength() {
        return kTotalBitLen;
    }

    /**
     * Calculates the ID which is 2^powerOfTwo bits greater than the current ID
     * modulo the maximum ID and returns it.
     * 
     * @param powerOfTwo
     *            Power of two which is added to the current ID. Must be a value
     *            of the interval [0, length-1], including both extremes.
     * @return ID which is 2^powerOfTwo bits greater than the current ID modulo
     *         the maximum ID.
     */
    public final ID addPowerOfTwo(final int powerOfTwo) {
        if ((powerOfTwo < 0) || (powerOfTwo >= kTotalBitLen)) {
            throw new IllegalArgumentException(
                    "The power of two is out of range! It must be in the interval [0, kTotalBitLen)");
        }

        return new ID(bitsetAddPwrOf2(id, powerOfTwo));
    }

    /**
     * Checks the given object for equality with this {@link ID}.
     * 
     * @param equalsTo
     *            Object to check equality with this {@link ID}.
     */
    public final boolean equals(Object equalsTo) {

        // check if given object has correct type
        if (equalsTo == null || !(equalsTo instanceof ID)) {
            return false;
        }

        // check if both byte arrays are equal by using the compareTo method
        return (this.compareTo((ID) equalsTo) == 0);

    }

    /**
     * Compare current ID with the given object. If either the object is not a
     * ID or both IDs' lengths do not match, a ClassCastException is thrown.
     * Otherwise both IDs are compared byte by byte.
     * 
     * @return -1, 0, or 1, if this ID is smaller, same size, or greater than
     *         the given object, respectively.
     */
    public final int compareTo(ID otherKey) throws ClassCastException {
        assert (id.size() >= kTotalBitLen); // size >= given minimum at creation.
        if (id.size() != otherKey.id.size()) {
            throw new ClassCastException("Only ID objects with same length can be " + "compared! This ID is "
                    + id.size() + " bits long while the other ID is " + otherKey.id.size() + " bits long.");
        }

        for (int i = 0; i < kTotalBitLen; ++i) {
            if (id.get(i) != otherKey.id.get(i))
                return id.get(i) ? 1 : -1;
        }

        return 0;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public final int hashCode() {
        return 19 + id.hashCode() * 13;
    }

    // this in (a, b)
    // NOTE: *not* [a, b], but (a, b)
    public boolean isInInterval(ID a, ID b) {
        return IdSpan.Inclusive(a, b).containsExcl(this);
    }

}