/***************************************************************************
* *
* FingerTable.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.service.impl;
import static de.uniba.wiai.lspi.util.logging.Logger.LogLevel.DEBUG;
import static de.uniba.wiai.lspi.util.logging.Logger.LogLevel.INFO;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.uniba.wiai.lspi.chord.com.Node;
import de.uniba.wiai.lspi.chord.data.ID;
import de.uniba.wiai.lspi.chord.data.URL;
import de.uniba.wiai.lspi.util.logging.Logger;
/**
* Stores references on the nodes in the finger table and provides methods for
* querying and manipulating this table.
*
* @author Karsten Loesing
* @version 1.0.5
*/
final class FingerTable {
/**
* ID of local node.
*/
private final ID localID;
/**
* Finger table data.
*/
private final Node[] remoteNodes;
/**
* Reference on parent object.
*/
private final References references;
/**
* Object logger.
*/
private Logger logger;
/**
* Creates an initially empty finger table. The table size is determined by
* the given ID's length. A reference on the parent object of type
* References is stored for being able to determine and disconnect unused
* references after removing them from the table.
*
* @param localID
* ID of local node.
* @param references
* Reference on parent object.
* @throws NullPointerException
* If either of the parameters is <code>null</code>.
*/
FingerTable(ID localID, References references) {
if (localID == null || references == null) {
throw new NullPointerException(
"Neither parameter of the constructor may contain a null "
+ "value!");
}
this.logger = Logger.getLogger(FingerTable.class.getCanonicalName() + "." + localID);
this.logger.debug("Logger initialized.");
this.references = references;
this.localID = localID;
this.remoteNodes = new Node[localID.getLength()];
}
/**
* Sets one table entry to the given reference.
*
* @param index
* Index of table entry.
* @param proxy
* Reference to store.
* @throws ArrayIndexOutOfBoundsException
* If given index is not contained in the finger table.
* @throws NullPointerException
* If given reference is <code>null</code>.
*/
private final void setEntry(int index, Node proxy) {
if (index < 0 || index >= this.remoteNodes.length) {
ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException(
"setEntry was invoked with an index out of array "
+ "bounds; index=" + index + ", length of array="
+ this.remoteNodes.length);
this.logger.error("Out of bounds!", e);
throw e;
}
if (proxy == null) {
NullPointerException e = new NullPointerException(
"Reference to proxy may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
this.remoteNodes[index] = proxy;
if (this.logger.isEnabledFor(DEBUG)) {
this.logger.debug("Entry " + index + " set to " + proxy.toString());
}
}
/**
* Returns the reference stored at the given index.
*
* @param index
* Index of entry to be returned.
* @throws ArrayIndexOutOfBoundsException
* If given index is not contained in the finger table.
* @return Reference stored at the given index.
*/
private final Node getEntry(int index) {
if (index < 0 || index >= this.remoteNodes.length) {
ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException(
"getEntry was invoked with an index out of array "
+ "bounds; index=" + index + ", length of array="
+ this.remoteNodes.length);
this.logger.error("Out of bounds!", e);
throw e;
}
return this.remoteNodes[index];
}
/**
* Sets the reference at the given index to <code>null</code> and triggers
* to disconnect that node, if no other reference to it is kept any more.
*
* @param index
* Index of entry to be set to <code>null</code>.
* @throws ArrayIndexOutOfBoundsException
* If given index is not contained in the finger table.
*/
private final void unsetEntry(int index) {
if (index < 0 || index >= this.remoteNodes.length) {
ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException(
"unsetEntry was invoked with an index out of array "
+ "bounds; index=" + index + ", length of array="
+ this.remoteNodes.length);
this.logger.error("Out of bounds!", e);
throw e;
}
// remember overwritten reference
Node overwrittenNode = this.getEntry(index);
// set reference to null
this.remoteNodes[index] = null;
if (overwrittenNode == null) {
this.logger.debug("unsetEntry did not change anything, because "
+ "entry was null before.");
} else {
// check if overwritten reference does not exist any more
this.references.disconnectIfUnreferenced(overwrittenNode);
if (this.logger.isEnabledFor(DEBUG)) {
this.logger.debug("Entry set to null: index=" + index
+ ", overwritten node=" + overwrittenNode.toString());
}
}
}
/**
* Adds the given reference to all finger table entries of which the start
* index is in the interval (local node ID, new node ID) and of which the
* current entry is <code>null</code> or further away from the local node
* ID than the new node ID (ie. the new node ID is in the interval (local
* node ID, currently stored node ID) ).
*
* @param proxy
* Reference to be added to the finger table.
* @throws NullPointerException
* If given reference is <code>null</code>.
*/
final void addReference(Node proxy) {
if (proxy == null) {
NullPointerException e = new NullPointerException(
"Reference to add may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
// for logging
int lowestWrittenIndex = -1;
int highestWrittenIndex = -1;
for (int i = 0; i < this.remoteNodes.length; i++) {
ID startOfInterval = this.localID.addPowerOfTwo(i);
if (!startOfInterval.isInInterval(this.localID, proxy.getNodeID())) {
break;
}
// for logging
if (lowestWrittenIndex == -1) {
lowestWrittenIndex = i;
}
highestWrittenIndex = i;
if (getEntry(i) == null) {
setEntry(i, proxy);
} else if (proxy.getNodeID().isInInterval(this.localID,
getEntry(i).getNodeID())) {
Node oldEntry = getEntry(i);
setEntry(i, proxy);
this.references.disconnectIfUnreferenced(oldEntry);
}
}
// logging
if (this.logger.isEnabledFor(DEBUG)) {
if (highestWrittenIndex == -1) {
this.logger
.debug("addReference did not add the given reference, "
+ "because it did not fit anywhere!");
}
}
if (this.logger.isEnabledFor(INFO)) {
if (highestWrittenIndex == lowestWrittenIndex) {
this.logger.info("Added reference to finger table entry "
+ highestWrittenIndex);
} else {
this.logger.info("Added reference to finger table entries "
+ lowestWrittenIndex + " to " + highestWrittenIndex);
}
}
}
/**
* Returns a copy of the finger table entries.
*
* @return Copy of finger table entries.
*/
final Node[] getCopyOfReferences() {
this.logger.debug("Returning copy of references.");
Node[] copy = new Node[this.remoteNodes.length];
System.arraycopy(this.remoteNodes, 0, copy, 0, this.remoteNodes.length);
return copy;
}
/**
* Returns a formatted string representation of this finger table.
*
* @return String representation containing one line per reference, together
* with the annotation which table entries contain this reference.
*/
public final String toString() {
StringBuilder result = new StringBuilder("Finger table:\n");
int lastIndex = -1;
ID lastNodeID = null;
URL lastNodeURL = null;
for (int i = 0; i < this.remoteNodes.length; i++) {
Node next = this.remoteNodes[i];
if (next == null) {
// row ended or did not even start
if ((lastIndex != -1) && (lastNodeID != null)) {
// row ended
result.append(" "
+ lastNodeID
+ ", "
+ lastNodeURL
+ " "
+ ((i - 1 - lastIndex > 0) ? "(" + lastIndex + "-"
+ (i - 1) + ")" : "(" + (i - 1) + ")")
+ "\n");
lastIndex = -1;
lastNodeID = null;
lastNodeURL = null;
} else {
// null at beginning
}
} else if (lastNodeID == null) {
// found first reference in a row
lastIndex = i;
lastNodeID = next.getNodeID();
lastNodeURL = next.getNodeURL();
} else if (!lastNodeID.equals(next.getNodeID())) {
// found different reference in finger table
result.append(" "
+ lastNodeID
+ ", "
+ lastNodeURL
+ " "
+ ((i - 1 - lastIndex > 0) ? "(" + lastIndex + "-"
+ (i - 1) + ")" : "(" + (i - 1) + ")") + "\n");
lastNodeID = next.getNodeID();
lastNodeURL = next.getNodeURL();
lastIndex = i;
} else {
// found next reference in a row
}
}
// display last row
if (lastNodeID != null && lastIndex != -1) {
// row ended
result.append(" "
+ lastNodeID
+ ", "
+ lastNodeURL
+ " "
+ ((this.remoteNodes.length - 1 - lastIndex > 0) ? "("
+ lastIndex + "-" + (this.remoteNodes.length - 1)
+ ")" : "(" + (this.remoteNodes.length - 1) + ")")
+ "\n");
lastNodeID = null;
}
return result.toString();
}
/**
* Removes all occurences of the given node from finger table.
*
* @param node1
* Reference to be removed from the finger table.
* @throws NullPointerException
* If given reference is <code>null</code>.
*/
final void removeReference(Node node1) {
if (node1 == null) {
NullPointerException e = new NullPointerException(
"removeReference cannot be invoked with value null!");
this.logger.error("Null pointer", e);
throw e;
}
// for logging
int lowestWrittenIndex = -1;
int highestWrittenIndex = -1;
// determine node reference with next larger ID than ID of node
// reference to remove
Node referenceForReplacement = null;
for (int i = this.localID.getLength() - 1; i >= 0; i--) {
Node n = this.getEntry(i);
if (node1.equals(n)) {
break;
}
if (n != null) {
referenceForReplacement = n;
}
}
// remove reference(s)
for (int i = 0; i < this.remoteNodes.length; i++) {
if (node1.equals(this.remoteNodes[i])) {
// for logging
if (lowestWrittenIndex == -1) {
lowestWrittenIndex = i;
}
highestWrittenIndex = i;
if (referenceForReplacement == null) {
unsetEntry(i);
} else {
setEntry(i, referenceForReplacement);
}
}
}
// try to add references of successor list to fill 'holes' in finger
// table
List<Node> referencesOfSuccessorList = new ArrayList<Node>(
this.references.getSuccessors());
referencesOfSuccessorList.remove(node1);
for (Node referenceToAdd : referencesOfSuccessorList) {
this.addReference(referenceToAdd);
}
// logging
if (this.logger.isEnabledFor(DEBUG)) {
if (highestWrittenIndex == -1) {
this.logger
.debug("removeReference did not remove the given reference, "
+ "because it did not exist in finger table "
+ "anywhere!");
} else if (highestWrittenIndex == lowestWrittenIndex) {
this.logger.debug("Removed reference from finger table entry "
+ highestWrittenIndex);
} else {
this.logger
.debug("Removed reference from finger table entries "
+ lowestWrittenIndex + " to "
+ highestWrittenIndex);
}
}
}
/**
* Determines closest preceding node of given id.
*
* @param key
* ID of which the closest preceding node shall be determined.
* @throws NullPointerException
* If given key is null.
* @return Reference to the node which most closely precedes the given ID.
* <code>null</code> if no node has been found.
*/
final Node getClosestPrecedingNode(ID key) {
if (key == null) {
NullPointerException e = new NullPointerException(
"ID to determine the closest preceding node may not be "
+ "null!");
this.logger.error("Null pointer", e);
throw e;
}
boolean debug = this.logger.isEnabledFor(DEBUG);
for (int i = this.remoteNodes.length - 1; i >= 0; i--) {
if (this.remoteNodes[i] != null
&& this.remoteNodes[i].getNodeID().isInInterval(
this.localID, key)) {
if (debug) {
this.logger.debug("Closest preceding node for ID " + key
+ " is " + this.remoteNodes[i].toString());
}
return this.remoteNodes[i];
}
}
if (debug) {
this.logger.debug("There is no closest preceding node for ID "
+ key + " -- returning null!");
}
return null;
}
/**
* Determines if the given reference is stored somewhere in the finger
* table.
*
* @param newReference
* Reference of which existence shall be determined.
* @throws NullPointerException
* If reference to look for is <code>null</code>.
* @return <code>true</code>, if the given reference exists in the finger
* table, or <code>false</code>, else.
*/
final boolean containsReference(Node newReference) {
if (newReference == null) {
NullPointerException e = new NullPointerException(
"Reference to check must not be null!");
this.logger.error("Null pointer", e);
throw e;
}
for (int i = 0; i < this.remoteNodes.length; i++) {
if (newReference.equals(this.remoteNodes[i])) {
return true;
}
}
return false;
}
/**
* @param i
* @return The first (i+1) entries of finger table. If there are fewer then
* i+1 entries only these are returned.
*
*
*
*/
final List<Node> getFirstFingerTableEntries(int i) {
Set<Node> result = new HashSet<Node>();
for (int j = 0; j < this.remoteNodes.length; j++) {
if (this.getEntry(j) != null) {
result.add(this.getEntry(j));
}
if (result.size() >= i) {
break;
}
}
return new ArrayList<Node>(result);
}
/**
* @return The all entries of finger table.
*
*
*
*/
final List<Node> getFingerTableEntries() {
Set<Node> result = new HashSet<Node>();
for (int j = 0; j < this.remoteNodes.length; j++) {
if (this.getEntry(j) != null) {
result.add(this.getEntry(j));
}
}
return new ArrayList<Node>(result);
}
}