/***************************************************************************
* *
* SuccessorList.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 java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import de.uniba.wiai.lspi.chord.com.CommunicationException;
import de.uniba.wiai.lspi.chord.com.Entry;
import de.uniba.wiai.lspi.chord.com.Node;
import de.uniba.wiai.lspi.chord.data.ID;
import de.uniba.wiai.lspi.util.logging.Logger;
import static de.uniba.wiai.lspi.util.logging.Logger.LogLevel.*;
/**
* Stores references on the next nodes in the Chord ring and provides methods
* for querying and manipulating this list.
*
* @author Karsten Loesing
* @version 1.0.5
*/
final class SuccessorList {
/**
* List storing the successor references in correct order.
*/
private List<Node> successors = null;
/**
* Local node ID - initialized in constructor.
*/
private ID localID;
/**
* Maximum number of references - initialized in constructor.
*/
private int capacity;
/**
* Reference on parent object of type References.
*/
private References references;
/**
* Reference on Entries.
*/
private Entries entries;
/**
* Object logger.
*/
private Logger logger;
/**
* Creates an empty list of successors.
*
* @param localID
* This node's ID; is used for comparison operations of other
* node references.
* @param numberOfEntries
* Number of entries to be stored in this successor list.
* @param parent
* Reference on this objects parent.
* @param entries
* Reference on the entry repository for replication purposes.
*/
SuccessorList(ID localID, int numberOfEntries, References parent,
Entries entries) {
this.logger = Logger.getLogger(SuccessorList.class + "." + localID);
this.logger.debug("Logger initialized.");
if (localID == null || parent == null || entries == null) {
NullPointerException e = new NullPointerException(
"Neither paremeter of this constructor may have value "
+ "null!");
this.logger.error("Null pointer", e);
throw e;
}
if (numberOfEntries < 1) {
throw new IllegalArgumentException(
"SuccessorList has to be at least of length 1! "
+ numberOfEntries + "is not a valid value!");
}
this.localID = localID;
this.capacity = numberOfEntries;
this.successors = new LinkedList<Node>();
this.references = parent;
this.entries = entries;
}
/**
* Adds a successor references, preserving ordering of list elements.
*
* @param nodeToAdd
* Reference to be added.
* @throws NullPointerException
* If node to add is <code>null</code>.
*/
final void addSuccessor(Node nodeToAdd) {
// check parameters
if (nodeToAdd == null) {
NullPointerException e = new NullPointerException(
"Parameter may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
boolean debug = logger.isEnabledFor(DEBUG);
boolean info = logger.isEnabledFor(INFO);
// is reference already contained in successor list?
if (this.successors.contains(nodeToAdd)) {
if (debug) {
this.logger.debug("Reference to new node "
+ nodeToAdd.toString()
+ " is not added to successor list, because it is "
+ "already contained.");
}
return;
}
// if new ID is between last ID in list and this node's own ID _AND_
// successor list already has maximum allowed list, the new reference IS
// NOT added!
if (this.successors.size() >= this.capacity
&& nodeToAdd.getNodeID().isInInterval(
this.successors.get(this.successors.size() - 1)
.getNodeID(), this.localID)) {
// do nothing
if (debug) {
this.logger.debug("Reference to new node "
+ nodeToAdd.toString()
+ " is not added to successor list, because the "
+ "list is already full and the new reference is "
+ "further away from the local node than all other "
+ "successors.");
}
return;
}
// insert node to successors
// insert before an existing element?
boolean inserted = false;
for (int i = 0; i < this.successors.size() && !inserted; i++) {
if (nodeToAdd.getNodeID().isInInterval(this.localID,
this.successors.get(i).getNodeID())) {
this.successors.add(i, nodeToAdd);
if (info) {
this.logger.info("Added new reference at position " + i);
}
inserted = true;
}
}
// insert at end if list not long enough
if (!inserted) {
this.successors.add(nodeToAdd);
if (info) {
this.logger.info("Added new reference to end of list");
}
inserted = true;
}
// determine ID range of entries this node is responsible for
// and replicate them on new node
ID fromID;
Node predecessor = this.references.getPredecessor();
if (predecessor != null) {
// common case: have a predecessor
fromID = predecessor.getNodeID();
} else {
// have no predecessor
// do I have any preceding node?
Node precedingNode = this.references
.getClosestPrecedingNode(this.localID);
if (precedingNode != null) {
// use ID of preceding node
fromID = precedingNode.getNodeID();
} else {
// use own ID (leads to replicating the whole ring); should not
// happen
fromID = this.localID;
}
}
// replicate entries from determined ID up to local ID
ID toID = this.localID;
Set<Entry> entriesToReplicate = this.entries.getEntriesInInterval(
fromID, toID);
try {
nodeToAdd.insertReplicas(entriesToReplicate);
this.logger.debug("Inserted replicas to new reference");
} catch (CommunicationException e) {
this.logger.warn("Entries could not be replicated to node "
+ nodeToAdd + "!", e);
}
// remove last element from this.successors, if maximum exceeded
if (this.successors.size() > this.capacity) {
Node nodeToDelete = this.successors.get(this.successors.size() - 1);
this.successors.remove(nodeToDelete);
// determine ID range of entries this node is responsible
// for and remove replicates of them from discarded successor
//
// Could be replaced by a new method:
// nodeToDelete.removeReplicas(fromID, toID);
// Set<Entry> replicatedEntries = this.entries.getEntriesInInterval(
// fromID, toID);
try {
// remove all replicas!
nodeToDelete.removeReplicas(this.localID, new HashSet<Entry>());
this.logger.debug("Removed replicas from node " + nodeToDelete);
} catch (CommunicationException e) {
this.logger.warn("Replicas of entries could not be removed "
+ "from node " + nodeToDelete + "!", e);
}
if (debug) {
this.logger.debug("If no other reference to node "
+ nodeToDelete
+ " exists any more, it is disconnected.");
}
this.references.disconnectIfUnreferenced(nodeToDelete);
}
}
/**
* Removes a successor reference without leaving a gap in the list
*
* @param nodeToDelete
* Reference to be removed.
* @throws NullPointerException
* If reference to remove is <code>null</code>.
*/
final void removeReference(Node nodeToDelete) {
if (nodeToDelete == null) {
NullPointerException e = new NullPointerException(
"Reference to remove may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
this.successors.remove(nodeToDelete);
// try to add references of finger table to fill 'hole' in successor
// list
List<Node> referencesOfFingerTable = this.references
.getFirstFingerTableEntries(this.capacity);
referencesOfFingerTable.remove(nodeToDelete);
for (Node referenceToAdd : referencesOfFingerTable) {
this.addSuccessor(referenceToAdd);
}
}
/**
* Returns an unmodifiable copy of the ordered successor list, starting with
* the closest following node.
*
* @return Unmodifiable copy of successor list.
*/
final List<Node> getReferences() {
return Collections.unmodifiableList(this.successors);
}
/**
* Returns a string representation of this successor list.
*
* @return String representation of list.
*/
public final String toString() {
StringBuilder result = new StringBuilder("Successor List:\n");
for (Node next : this.successors) {
result.append(" " + next.getNodeID().toString() + ", "
+ next.getNodeURL() + "\n");
}
return result.toString();
}
/**
* Returns closest preceding node of given ID.
*
* @param idToLookup
* ID of which closest preceding node is sought-after.
* @throws NullPointerException
* If ID to look up is <code>null</code>.
* @return Reference on closest preceding node of given ID.
*/
final Node getClosestPrecedingNode(ID idToLookup) {
if (idToLookup == null) {
NullPointerException e = new NullPointerException(
"ID to look up may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
for (int i = this.successors.size() - 1; i >= 0; i--) {
Node nextNode = this.successors.get(i);
if (nextNode.getNodeID().isInInterval(this.localID, idToLookup)) {
return nextNode;
}
}
return null;
}
/**
* Determines if the given reference is contained in this successor list.
*
* @param nodeToLookup
* Reference to look up.
* @throws NullPointerException
* If node to look up is <code>null</code>.
* @return <code>true</code>, if reference is contained, and
* <code>false</code>, else.
*/
final boolean containsReference(Node nodeToLookup) {
if (nodeToLookup == null) {
NullPointerException e = new NullPointerException(
"Node to look up may not be null!");
this.logger.error("Null pointer", e);
throw e;
}
return this.successors.contains(nodeToLookup);
}
/**
* Returns the reference on the direct successor; may be <code>null</code>,
* if the list is empty.
*
* @return Direct successor (or <code>null</code> if list is empty).
*/
final Node getDirectSuccessor() {
if (this.successors.size() == 0) {
return null;
}
return this.successors.get(0);
}
/**
* @return the capacity
*/
final int getCapacity() {
return capacity;
}
final int getSize() {
return this.successors.size();
}
}