com.offbynull.peernetic.playground.chorddht.model.FingerTable.java Source code

Java tutorial

Introduction

Here is the source code for com.offbynull.peernetic.playground.chorddht.model.FingerTable.java

Source

/*
 * Copyright (c) 2013-2014, Kasra Faghihi, All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.offbynull.peernetic.playground.chorddht.model;

import com.offbynull.peernetic.common.identification.Id;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.lang3.Validate;

/**
 * Holds on to routing information. For more information on how a finger table
 * operates, please refer to the Chord research paper. This implementation makes
 * some minor additions to the original finger table algorithm to ensure that
 * that there aren't any inconsistencies. That is, this implementation
 * guarantees that...
 * <ol>
 * <li>
 * Fingers that point to the base pointer show up for a contiguous range from
 * the entry after the last non-base entry (or 0 if there are no non-base
 * entries) all the way to the last entry in the finger table. For example:
 * <p/>
 * Imagine that you have just created a finger table and the base id is 0:<br/>
 * [0, 0, 0, 0, 0, 0].
 * <p/>
 * You insert a finger with id 8:<br/>
 * [8, 8, 8, 8, 0, 0].
 * <p/>
 * </li>
 * <li>An inserted finger will propagate backwards until it finds an entry that
 * isn't the base entry and isn't the same id as the id being replaced, but is
 * greater than or equal to the expected id. For example:
 * <p/>
 * Imagine that you have just created a finger table and the base id is 0:<br/>
 * [0, 0, 0, 0, 0, 0].
 * <p/>
 * You insert a finger with id 16:<br/>
 * [16, 16, 16, 16, 16, 0].
 * <p/>
 * You insert a finger with id 2:<br/>
 * [2, 2, 16, 16, 16, 0].
 * <p/>
 * You insert a finger with id 8:<br/>
 * [2, 2, 8, 8, 16, 0].
 * <p/>
 * You insert a finger with id 4:<br/>
 * [2, 2, 4, 8, 16, 0].
 * </li>
 * <li>A finger being removed will propagate backwards until it finds an entry
 * that isn't the base (it will never be base -- based on previous guarantees)
 * and isn't the same id as the id being removed. The replacement value for the
 * finger being removed will be the finger in front of it (or base if it's the
 * last finger and there are no other fingers in front of it). For example:
 * <p/>
 * Imagine that you have just created a finger table and the base id is 0:<br/>
 * [0, 0, 0, 0, 0, 0].
 * <p/>
 * You insert a finger with id 16:<br/>
 * [16, 16, 16, 16, 16, 0].
 * <p/>
 * You insert a finger with id 2:<br/>
 * [2, 2, 16, 16, 16, 0].
 * <p/>
 * You insert a finger with id 8:<br/>
 * [2, 2, 8, 8, 16, 0].
 * <p/>
 * You remove a finger with id 8:<br/>
 * [2, 2, 16, 16, 16, 0].
 * <p/>
 * You remove a finger with id 16:<br/>
 * [2, 2, 0, 0, 0, 0].
 * <p/>
 * You remove a finger with id 2:<br/>
 * [0, 0, 0, 0, 0, 0].
 * </li>
 * </ol>
 *
 * @param <A> address type
 * @author Kasra Faghihi
 */
public final class FingerTable<A> {

    /**
     * Internal table that keeps track of fingers.
     */
    private List<InternalEntry> table;
    /**
     * Base (self) pointer.
     */
    private InternalPointer basePtr;
    /**
     * The bit count in {@link #basePtr}.
     */
    private int bitCount;

    /**
     * Constructs a {@link FingerTable}. All fingers are initialized to
     * {@code base}.
     *
     * @param basePtr base pointer
     * @throws NullPointerException if any arguments are {@code null}
     */
    public FingerTable(InternalPointer basePtr) {
        Validate.notNull(basePtr);
        Id baseId = basePtr.getId();
        Validate.isTrue(IdUtils.isUseableId(baseId)); // make sure satisfies 2^n-1

        this.basePtr = basePtr;

        this.bitCount = IdUtils.getBitLength(baseId);
        byte[] limit = baseId.getLimitAsByteArray();

        table = new ArrayList<>(bitCount);
        for (int i = 0; i < bitCount; i++) {
            BigInteger data = BigInteger.ONE.shiftLeft(i);
            byte[] offsetIdRaw = data.toByteArray();
            Id offsetId = new Id(offsetIdRaw, limit);
            Id expectedId = Id.add(baseId, offsetId);

            InternalEntry te = new InternalEntry();
            te.expectedId = expectedId;
            te.pointer = basePtr;

            table.add(te);
        }
    }

    /**
     * Searches the finger table for the closest to the id being searched for (closest in terms of being {@code <}).
     *
     * @param id id being searched for
     * @return closest preceding pointer
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code id}'s has a different limit bit size than base pointer's id
     */
    public Pointer findClosestPreceding(Id id) {
        Validate.notNull(id);
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        Id selfId = basePtr.getId();

        InternalEntry foundEntry = null;
        ListIterator<InternalEntry> lit = table.listIterator(table.size());
        while (lit.hasPrevious()) {
            InternalEntry ie = lit.previous();
            // if finger[i] exists between n (exclusive) and id (exclusive)
            // then return it
            Id fingerId = ie.pointer.getId();
            if (fingerId.isWithin(selfId, false, id, false)) {
                foundEntry = ie;
                break;
            }
        }

        return foundEntry == null ? basePtr : foundEntry.pointer;
    }

    /**
     * Searches the finger table for the closest id to the id being searched for (closest in terms of being {@code <=} to).
     *
     * @param id id being searched for
     * @return closest pointer (closest in terms of being {@code <=} to)
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code id}'s has a different limit bit size than base pointer's id
     */
    public Pointer findClosest(Id id) {
        Validate.notNull(id);
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        Id selfId = basePtr.getId();

        InternalEntry foundEntry = null;
        ListIterator<InternalEntry> lit = table.listIterator(table.size());
        while (lit.hasPrevious()) {
            InternalEntry ie = lit.previous();
            // if finger[i] exists between n (exclusive) and id (exclusive)
            // then return it
            Id fingerId = ie.pointer.getId();
            if (fingerId.isWithin(selfId, false, id, true)) {
                foundEntry = ie;
                break;
            }
        }

        return foundEntry == null ? basePtr : foundEntry.pointer;
    }

    /**
     * Searches the finger table for the closest id to the id being searched for (closest in terms of being {@code <=} to), but skips
     * certain ids.
     *
     * @param id id being searched for
     * @param skipIds ids being skipped
     * @return closest pointer (closest in terms of being {@code <=} to)
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code id}'s has a different limit bit size than base pointer's id
     */
    public Pointer findClosest(Id id, Id... skipIds) {
        Validate.notNull(id);
        Validate.noNullElements(skipIds);
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        List<Id> skipIdList = Arrays.asList(skipIds);
        Id selfId = basePtr.getId();

        InternalEntry foundEntry = null;
        ListIterator<InternalEntry> lit = table.listIterator(table.size());
        while (lit.hasPrevious()) {
            InternalEntry ie = lit.previous();
            Id fingerId = ie.pointer.getId();
            // if finger should be skipped, ignore it
            if (skipIdList.contains(fingerId)) {
                continue;
            }
            // if finger[i] exists between n (exclusive) and id (exclusive)
            // then return it
            if (fingerId.isWithin(selfId, false, id, true)) {
                foundEntry = ie;
                break;
            }
        }

        return foundEntry == null ? basePtr : foundEntry.pointer;
    }

    /**
     * Puts a pointer in to the finger table. See the constraints / guarantees mentioned in the class Javadoc: {@link FingerTable}.
     * <p/>
     * This method automatically determines the correct position for the finger.  The old pointer in that finger will be replaced by
     * {@code ptr}.
     *
     * @param ptr pointer to put in as finger
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code ptr}'s id has a different limit bit size than the base pointer's id, or if {@code ptr} has 
     * an id that matches the base pointer's id
     */
    public void put(ExternalPointer<A> ptr) {
        Validate.notNull(ptr);

        Id id = ptr.getId();
        Id baseId = basePtr.getId();

        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);
        Validate.isTrue(!id.equals(baseId));

        // search for position to insert to
        int replacePos = -1;
        for (int i = 0; i < table.size(); i++) {
            InternalEntry ie = table.get(i);
            Id goalId = ie.expectedId;
            int compVal = Id.comparePosition(baseId, goalId, id);
            if (compVal < 0) {
                replacePos = i;
            } else if (compVal == 0) {
                replacePos = i;
                break;
            }
        }

        if (replacePos == -1) {
            return;
        }

        // replace in table
        InternalEntry entry = table.get(replacePos);
        entry.pointer = ptr;

        // replace immediate preceding neighbours if they exceed replacement
        for (int i = replacePos - 1; i >= 0; i--) {
            InternalEntry priorEntry = table.get(i);
            Id priorEntryId = priorEntry.pointer.getId();

            if (Id.comparePosition(baseId, priorEntryId, id) > 0 || priorEntryId.equals(baseId)) {
                priorEntry.pointer = ptr;
            } else {
                break;
            }
        }
    }

    /**
     * Replaces a pointer in to the finger table. Similar to
     * {@link #put(com.offbynull.peernetic.demos.chord.messages.core.ExternalPointer)}, but makes sure that {@code ptr} is less than or
     * equal to the expected id before putting it in.
     * <p/>
     * For example, imagine a finger table for a base pointer with an id of 0 and a bit count of 3. This is what the initial table would
     * look like...
     * <pre>
     * Index 0 = id:0 (base)
     * Index 1 = id:0 (base)
     * Index 2 = id:0 (base)
     * </pre>
     * If a value pointer with id of 6 were put in here, then a pointer with id of 1 were put in here, the table would look like this...
     * <pre>
     * Index 0 = id:1 (base)
     * Index 1 = id:6 (base)
     * Index 2 = id:6 (base)
     * </pre>
     * If this method were called with a pointer that had id of 7, nothing would happen. If this method were called with a pointer that had
     * id of 5, then the table would be adjusted to look like this...
     * <pre>
     * Index 0 = id:1 (base)
     * Index 1 = id:5 (base)
     * Index 2 = id:5 (base)
     * </pre>
     *
     * @return {@code true} if one or more entries were replaced, {@code false} otherwise
     * @param ptr pointer to add in as finger
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code ptr}'s id has a different limit bit size than the base pointer's id, or if {@code ptr} has
     * an id that matches the base pointer's id
     */
    public boolean replace(ExternalPointer<A> ptr) {
        Validate.notNull(ptr);
        Validate.isTrue(IdUtils.getBitLength(ptr.getId()) == bitCount);

        Id id = ptr.getId();
        Id baseId = basePtr.getId();

        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);
        Validate.isTrue(!id.equals(baseId));

        // search for position to insert to
        int replacePos = -1;
        for (int i = 0; i < table.size(); i++) {
            InternalEntry ie = table.get(i);
            Id goalId = ie.expectedId;
            int compVal = Id.comparePosition(baseId, goalId, id);
            if (compVal < 0) {
                replacePos = i;
            } else if (compVal == 0) {
                replacePos = i;
                break;
            }
        }

        if (replacePos == -1) {
            return false;
        }

        // check if can be replaced -- if so, replace.
        InternalEntry entry = table.get(replacePos);
        Id entryId = entry.pointer.getId();

        Id oldId;
        if (Id.comparePosition(baseId, id, entryId) < 0 || entryId.equals(baseId)) {
            oldId = entry.pointer.getId();
            entry.pointer = ptr;

            // replace immediate preceding neighbours if they = old value
            for (int i = replacePos - 1; i >= 0; i--) {
                InternalEntry priorEntry = table.get(i);
                Id priorEntryId = priorEntry.pointer.getId();

                if (priorEntryId.equals(oldId)) {
                    priorEntry.pointer = ptr;
                } else {
                    break;
                }
            }
        }

        return true;
    }

    /**
     * Get the base pointer.
     * @return base pointer
     */
    public InternalPointer getBase() {
        return basePtr;
    }

    /**
     * Get the id from the base pointer. Equivalent to calling {@code getBase().getId()}.
     *
     * @return base pointer id
     */
    public Id getBaseId() {
        return basePtr.getId();
    }

    /**
     * Get the last/maximum finger that isn't set to base. If no such finger is found, gives back {@code null}. See the
     * constraints/guarantees mentioned in the Javadoc header {@link FingerTable}.
     * @return last/max non-base finger, or {@code null} if no such finger exists
     */
    public ExternalPointer<A> getMaximumNonBase() {
        for (int i = bitCount - 1; i >= 0; i--) {
            InternalEntry ie = table.get(i);
            if (!(ie.pointer instanceof InternalPointer)) { // equiv to ie.actualPointer.getId().equals(basePtr.getId()), since we're
                                                            // only ever allowed to have references to ourself / our own id via a
                                                            // InternalPointer
                return (ExternalPointer<A>) ie.pointer;
            }
        }

        return null;
    }

    /**
     * Get finger[0] if it doesn't match the base id. If it does match the base id, gives back {@code null}
     * @return finger[0], or {@code null} if finger[0] is base
     */
    public ExternalPointer<A> getMinimumNonBase() {
        InternalEntry ie = table.get(0);

        if (!(ie.pointer instanceof InternalPointer)) { // equiv to ie.actualPointer.getId().equals(basePtr.getId()), since we're
                                                        // only ever allowed to have references to ourself / our own id via a
                                                        // InternalPointer
            return (ExternalPointer<A>) ie.pointer;
        }

        return null;
    }

    /**
     * Get the finger at a specific index.
     *
     * @param idx finger index
     * @return finger at index {@code idx}
     * @throws IllegalArgumentException if {@code idx < 0 || idx > table.size()} (size of table = bit size of base pointer's limit)
     */
    public Pointer get(int idx) {
        Validate.inclusiveBetween(0, table.size() - 1, idx);

        InternalEntry ie = table.get(idx);
        return ie.pointer;
    }

    /**
     * Get the id expected for a finger position. For example, if base id is 0, finger pos 0 expects 1, finger pos 1 expects 2, finger pos 2
     * expects 4, finger pos 3 expects 8, etc... For more information, see Chord research paper.
     * @param idx finger position to get expected id for
     * @return expected id for a specific finger position
     * @throws IllegalArgumentException if {@code idx < 0 || idx > table.size()} (size of table = bit size of base pointer's limit)
     */
    public Id getExpectedId(int idx) {
        Validate.inclusiveBetween(0, table.size() - 1, idx);

        InternalEntry ie = table.get(idx);
        return ie.expectedId;
    }

    /**
     * Get the id of other nodes that should have this node in its finger table. For example, for a 16 node ring with base id 8, router
     * position 0 expects 7, router position 1 expects 6, router position 2 expects 4, and router position 3 expects 0. For more
     * information, see Chord research paper.
     * @param idx router position to get expected id for
     * @return expected id for a specific router position
     * @throws IllegalArgumentException if {@code idx < 0 || idx > table.size()} (size of table = bit size of base pointer's limit)
     */
    public Id getRouterId(int idx) {
        Validate.inclusiveBetween(0, table.size() - 1, idx);

        BigInteger data = BigInteger.ONE.shiftLeft(idx);
        byte[] offsetIdRaw = data.toByteArray();
        Id offsetId = new Id(offsetIdRaw, basePtr.getId().getLimitAsByteArray());
        Id routerId = Id.subtract(getBaseId(), offsetId);

        return routerId;
    }

    /**
     * Removes a pointer in to the finger table. If the pointer doesn't exist in the finger table, does nothing. See the
     * constraints/guarantees mentioned in the class Javadoc: {@link FingerTable}.
     * @param ptr pointer to put in as finger
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code ptr}'s id has a different limit bit size than the base pointer's id, or if {@code ptr} has
     * an id that matches the base pointer's id
     */
    public void remove(ExternalPointer<A> ptr) {
        Validate.notNull(ptr);
        Validate.isTrue(IdUtils.getBitLength(ptr.getId()) == bitCount);

        Id id = ptr.getId();
        A address = ptr.getAddress();
        Id baseId = basePtr.getId();

        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);
        Validate.isTrue(!id.equals(baseId));

        ListIterator<InternalEntry> lit = table.listIterator(table.size());
        while (lit.hasPrevious()) {
            InternalEntry ie = lit.previous();

            if (ie.pointer instanceof InternalPointer) { // don't bother inspecting internal pointers since we can only remove
                                                         // external pointers
                continue;
            }

            ExternalPointer<A> testPtr = (ExternalPointer<A>) ie.pointer;
            Id testId = testPtr.getId();
            A testAddress = testPtr.getAddress();

            if (id.equals(testId) && address.equals(testAddress)) {
                remove(lit.previousIndex() + 1);
                break;
            }
        }
    }

    /**
     * Removes the pointer at index {@code idx} of the finger table. See the constraints / guarantees mentioned in the class Javadoc:
     * {@link FingerTable}.
     *
     * @param idx finger position to clear
     * @throws IllegalArgumentException if {@code ptr}'s id has a different limit bit size than the base pointer's id, or if {@code ptr} has
     * an id that matches the base pointer's id.
     */
    private void remove(int idx) {
        Validate.inclusiveBetween(0, bitCount - 1, idx);

        Id baseId = basePtr.getId();

        // save existing id
        InternalEntry entry = table.get(idx);
        Id oldId = entry.pointer.getId();

        if (oldId.equals(baseId)) {
            // nothing to remove if self... all forward entries sohuld
            // also be self in this case
            return;
        }

        // get next id in table, if available
        Pointer nextPtr;

        if (idx < bitCount - 1) {
            // set values if there's a next available (if we aren't at ceiling)
            InternalEntry nextEntry = table.get(idx + 1);
            nextPtr = nextEntry.pointer;
        } else {
            // set values to self if we are at the ceiling (full circle)
            nextPtr = basePtr;
        }

        // replace prior ids with next id if prior same as old id and is
        // contiguous... prior ids will never be greater than the old id
        for (int i = idx; i >= 0; i--) {
            InternalEntry priorEntry = table.get(i);
            Id priorEntryId = priorEntry.pointer.getId();

            if (priorEntryId.equals(oldId)) {
                priorEntry.pointer = nextPtr;
            } else {
                break;
            }
        }
    }

    /**
     * Gets finger before {@code id}.
     *
     * @param id id to search for
     * @return finger before (@code id}, or {@code null} if not found
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code id} has a different limit bit size than base pointer's id, or if {@code id} is equivalent
     * to base pointer's id
     */
    public Pointer getBefore(Id id) {
        Validate.notNull(id);
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        Id baseId = basePtr.getId();

        if (id.equals(baseId)) {
            throw new IllegalArgumentException();
        }

        ListIterator<InternalEntry> lit = table.listIterator(table.size());
        while (lit.hasPrevious()) {
            InternalEntry ie = lit.previous();
            Id testId = ie.pointer.getId();

            if (Id.comparePosition(baseId, id, testId) > 0) {
                return lit.hasPrevious() ? lit.previous().pointer : null;
            }
        }

        return null;
    }

    /**
     * Removes all fingers before {@code id} (does not remove {@code id} itself).
     *
     * @param id id of which all fingers before it will be removed
     * @return number of fingers that were cleared
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code id} has a different limit bit size than base pointer's id, or if {@code id} is equivalent
     * to base pointer's id
     */
    public int clearBefore(Id id) {
        Validate.notNull(id);
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        Id baseId = basePtr.getId();

        if (id.equals(baseId)) {
            throw new IllegalArgumentException();
        }

        ListIterator<InternalEntry> lit = table.listIterator(table.size());
        while (lit.hasPrevious()) {
            InternalEntry ie = lit.previous();
            Id testId = ie.pointer.getId();

            if (Id.comparePosition(baseId, id, testId) > 0) {
                int position = lit.previousIndex() + 1;
                clearBefore(position);
                return position;
            }
        }

        return 0;
    }

    /**
     * Removes all fingers before position {@code idx} (does not remove finger at {@code idx}).
     * @param idx position which all fingers before it will be removed
     * @throws IllegalArgumentException if {@code idx < 0 || idx > table.size()}
     */
    private void clearBefore(int idx) {
        Validate.inclusiveBetween(0, bitCount - 1, idx);

        // get next id in table, if available
        Pointer nextPtr;

        if (idx < bitCount - 1) {
            // set values if there's a next available (if we aren't at ceiling)
            InternalEntry nextEntry = table.get(idx + 1);
            nextPtr = nextEntry.pointer;
        } else {
            // set values to self if we are at the ceiling (full circle)
            nextPtr = basePtr;
        }

        // replace prior ids with next id... prior ids will never be greater
        // than the id at the position we're removing
        for (int i = idx; i >= 0; i--) {
            InternalEntry priorEntry = table.get(i);
            priorEntry.pointer = nextPtr;
        }
    }

    /**
     * Removes all fingers after {@code id} (does not remove {@code id} itself).
     * @param id id of which all fingers after it will be removed
     * @return number of fingers that were cleared
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code id} has a different limit bit size than base pointer's id
     */
    public int clearAfter(Id id) {
        Validate.notNull(id);
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        Id baseId = basePtr.getId();

        ListIterator<InternalEntry> lit = table.listIterator();
        while (lit.hasNext()) {
            InternalEntry ie = lit.next();
            Pointer testPtr = ie.pointer;
            Id testId = testPtr.getId();

            if (Id.comparePosition(baseId, id, testId) < 0) {
                int position = lit.nextIndex() - 1;
                clearAfter(position);
                return bitCount - position;
            }
        }

        return 0;
    }

    /**
     * Removes all fingers after position {@code idx} (does not remove finger at {@code idx}).
     * @param idx position which all fingers after it will be removed
     * @throws IllegalArgumentException if {@code idx < 0 || idx > table.size()}
     */
    private void clearAfter(int idx) {
        Validate.inclusiveBetween(0, bitCount - 1, idx);

        // replace entries with self id all the way till the end...
        for (int i = idx; i < bitCount; i++) {
            InternalEntry priorEntry = table.get(i);
            priorEntry.pointer = basePtr;
        }
    }

    /**
     * Clears the finger table. All fingers will be set to the base pointer.
     */
    public void clear() {
        // replace entries with self id all the way till the end...
        for (int i = 0; i < bitCount; i++) {
            InternalEntry priorEntry = table.get(i);
            priorEntry.pointer = basePtr;
        }
    }

    /**
     * Searches the finger table for the right-most occurrence of {@code ptr}.
     * @param ptr pointer to search for
     * @return index of occurrence, or -1 if not found
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code ptr}'s id has a different limit bit size than the base pointer's id
     */
    public int getMaximumIndex(Pointer ptr) {
        Validate.notNull(ptr);
        Validate.isTrue(IdUtils.getBitLength(ptr.getId()) == bitCount);

        Id id = ptr.getId();
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        ListIterator<InternalEntry> lit = table.listIterator(table.size());
        while (lit.hasPrevious()) {
            InternalEntry ie = lit.previous();

            if (ie.pointer.equals(ptr)) {
                return lit.previousIndex() + 1;
            }
        }

        return -1;
    }

    /**
     * Searches the finger table for the left-most occurrence of {@code ptr}.
     * @param ptr pointer to search for
     * @return index of occurrence, or -1 if not found
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code ptr}'s id has a different limit bit size than the base pointer's id
     */
    public int getMinimumIndex(Pointer ptr) {
        Validate.notNull(ptr);
        Validate.isTrue(IdUtils.getBitLength(ptr.getId()) == bitCount);

        Id id = ptr.getId();
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        ListIterator<InternalEntry> lit = table.listIterator();
        while (lit.hasNext()) {
            InternalEntry ie = lit.next();

            if (ie.pointer.equals(ptr)) {
                return lit.nextIndex() - 1;
            }
        }

        return -1;
    }

    /**
     * Searches the finger table for {@code ptr}.
     * @param ptr pointer to search for
     * @return {@code true} if found, {@code false} otherwise
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code ptr}'s id has a different limit bit size than the base pointer's id
     */
    public boolean contains(Pointer ptr) {
        return getMinimumIndex(ptr) != -1;
    }

    /**
     * Searches the finger table for {@code id}.
     * @param id id to search for
     * @return {@code true} if found, {@code false} otherwise
     * @throws NullPointerException if any arguments are {@code null}
     * @throws IllegalArgumentException if {@code id} has a different limit bit size than the base pointer's id
     */
    public boolean contains(Id id) {
        Validate.notNull(id);
        Validate.isTrue(IdUtils.getBitLength(id) == bitCount);

        ListIterator<InternalEntry> lit = table.listIterator();
        while (lit.hasNext()) {
            InternalEntry ie = lit.next();

            if (ie.pointer.getId().equals(id)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks to see if all entries in the finger table are set to base pointer.
     * @return {@code true} if all entries are set to base pointer, {@code false} otherwise
     */
    public boolean isPointingToBase() {
        if (table.isEmpty()) {
            return false;
        }

        InternalEntry ie = table.get(0);
        return ie.pointer instanceof InternalPointer;
    }

    /**
     * Dumps the fingers.
     *
     * @return list of fingers
     */
    public List<Pointer> dump() {
        List<Pointer> ret = new ArrayList<>(table.size());
        table.stream().forEach(ie -> ret.add(ie.pointer));

        return ret;
    }

    /**
     * Internal class to keep track of a finger.
     */
    private final class InternalEntry {

        /**
         * Desired id for finger.
         */
        private Id expectedId;
        /**
         * Pointer this finger is pointing to (if self set to {@link InternalPointer}, otherwise set to {@link ExternalPointer}).
         */
        private Pointer pointer;
    }
}