/***************************************************************************
* *
* 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.math.BigInteger;
import java.util.Arrays;
//import org.apache.log4j.Logger;
/**
* 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 {
/**
*
*/
private static final long serialVersionUID = 6860626236168125168L;
private static final int HEX = 2;
@SuppressWarnings("unused")
private static final int DEC = 1;
@SuppressWarnings("unused")
private static final int BIN = 0;
/**
* The representation of an id returned as String when {@link #toString()}
* is invoked. Is intialized with help of property
* <code>de.uniba.wiai.lspi.chord.data.ID.displayed.representation</code>.
* Possible values: <br/><br/>
*
* <code>0 = BIN</code>, binary<br/> <code>1 = DEC</code>, decimal<br/>
* <code>2 = HEX</code>, hexadecimal<br/>
*/
private static int displayedRepresentation = HEX;
// static initializer for displayedRepresentation
static {
String property = System.getProperty(ID.class.getName()
+ ".displayed.representation");
if (property != null && property.length() > 0) {
displayedRepresentation = Integer.parseInt(property);
}
}
/**
* The number of (highest) bytes of an id returned as String when
* {@link #toString()} is invoked. Is intialized with help of property
* <code>de.uniba.wiai.lspi.chord.data.ID.number.of.displayed.bytes</code>.
*/
private static int numberOfDisplayedBytes = Integer.MAX_VALUE;
// static initializer for numberOfDisplayedBytes
static {
String numberProperty = System.getProperty(ID.class.getName()
+ ".number.of.displayed.bytes");
if (numberProperty != null && numberProperty.length() > 0) {
numberOfDisplayedBytes = Integer.parseInt(numberProperty);
}
}
/**
* The bytes representing the id.
*/
private final byte[] id;
/**
* Creates a new ID consisting of the given byte[] array. The ID is assumed
* to have (ID.length * 8) bits. It must have leading zeros if its value has
* fewer digits than its maximum length.
*
* @param id1
* Byte array containing the ID.
*/
public ID(byte[] id1) {
this.id = new byte[id1.length];
System.arraycopy(id1, 0, this.id, 0, id1.length);
}
/**
* Creates an ID instance by copying the byte array from the given value to
* the new ID. Since BigInteger stores the byte array as a two's-complement
* and ID doesn't (ID can only hold positive values) the first byte is
* ignored if it is 0, otherwise it's included. Thus, you shouldn't provide
* a negative value which would result in a wrong ID since IDs cannot be
* negative.
*/
public static ID valueOf(final BigInteger value) {
final byte[] barr = value.toByteArray();
final int start;
if (barr.length < 20) {
final byte[] b = new byte[20];
Arrays.fill(b, (byte) 0);
System.arraycopy(barr, 0, b, 20 - barr.length, barr.length);
return new ID(b);
} else if (barr.length > 20) {
start = 1;
} else {
start = 0;
}
return new ID(Arrays.copyOfRange(barr, start, barr.length));
}
public BigInteger toBigInteger() {
final byte[] barr = new byte[getBytes().length + 1];
barr[0] = 0;
System.arraycopy(getBytes(), 0, barr, 1, getBytes().length);
return new BigInteger(barr);
}
public ID add(ID value) {
return new ID(toBigInteger().add(value.toBigInteger()).toByteArray());
}
public ID add(long value) {
return new ID(toBigInteger().add(BigInteger.valueOf(value)).toByteArray());
}
public ID subtract(ID value) {
return new ID(toBigInteger().subtract(value.toBigInteger()).toByteArray());
}
public ID subtract(int value) {
return new ID(toBigInteger().subtract(BigInteger.valueOf(value)).toByteArray());
}
public ID multiply(long value) {
return new ID(toBigInteger().multiply(BigInteger.valueOf(value)).toByteArray());
}
public ID divide(long value) {
return new ID(toBigInteger().divide(BigInteger.valueOf(value)).toByteArray());
}
public ID mod(ID id) {
return new ID(toBigInteger().mod(id.toBigInteger()).toByteArray());
}
// ID getUnsignedId(byte[] id) {
// ID result;
// if (id.length == 21 && id[0] == 0) {
// byte[] tmp = new byte[id.length - 1];
// System.arraycopy(id, 1, tmp, 0, tmp.length);
// result = new ID(tmp);
// } else {
// result = new ID(id);
// }
// return result;
// }
public byte[] getBytes() {
return id;
}
/**
* Representation of this as a String.
*/
private transient String stringRepresentation = null;
/**
* Returns a string of the decimal representation of this ID, including
* leading zeros.
*
* @return Decimal string of ID
*/
public final String toString() {
if (this.stringRepresentation == null) {
int rep = ID.displayedRepresentation;
switch (rep) {
case 0:
this.stringRepresentation = this
.toBinaryString(ID.numberOfDisplayedBytes);
break;
case 1:
this.stringRepresentation = this
.toDecimalString(ID.numberOfDisplayedBytes);
break;
default:
this.stringRepresentation = this
.toHexString(ID.numberOfDisplayedBytes);
}
}
return this.stringRepresentation;
}
/**
* 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, this.id.length]
int displayBytes = Math.max(1, Math.min(numberOfBytes, this.id.length));
StringBuilder result = new StringBuilder();
for (int i = 0; i < displayBytes; i++) {
String block = Integer.toHexString(this.id[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(this.id.length);
}
/**
* Returns a string of the decimal representation of the first
* <code>n</code> bytes of this ID, including leading zeros.
*
* @param numberOfBytes
*
* @return Hex string of ID
*/
public final String toDecimalString(int numberOfBytes) {
// number of displayed bytes must be in interval [1, this.id.length]
int displayBytes = Math.max(1, Math.min(numberOfBytes, this.id.length));
StringBuilder result = new StringBuilder();
for (int i = 0; i < displayBytes; i++) {
String block = Integer.toString(this.id[i] & 0xff);
result.append(block + " ");
}
return result.toString();
}
/**
* Returns a string of the decimal representation of this ID, including
* leading zeros.
*
* @return Decimal string of ID
*/
public final String toDecimalString() {
return this.toDecimalString(this.id.length);
}
/**
* Returns a string of the binary representation of the first <code>n</code>
* bytes of this ID, including leading zeros.
*
* @param numberOfBytes
*
* @return Hex string of ID
*/
public final String toBinaryString(int numberOfBytes) {
// number of displayed bytes must be in interval [1, this.id.length]
int displayBytes = Math.max(1, Math.min(numberOfBytes, this.id.length));
StringBuilder result = new StringBuilder();
for (int i = 0; i < displayBytes; i++) {
String block = Integer.toBinaryString(this.id[i] & 0xff);
// add leading zero to block, if necessary
while (block.length() < 8) {
block = "0" + block;
}
result.append(block + " ");
}
return result.toString();
}
/**
* Returns a string of the binary representation of this ID, including
* leading zeros.
*
* @return Binary string of ID
*/
public final String toBinaryString() {
return this.toBinaryString(this.id.length);
}
/**
* 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 this.id.length * 8;
}
/**
* 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(int powerOfTwo) {
if (powerOfTwo < 0 || powerOfTwo >= (this.id.length * 8)) {
throw new IllegalArgumentException(
"The power of two is out of range! It must be in the interval "
+ "[0, length-1]");
}
// copy ID
byte[] copy = new byte[this.id.length];
System.arraycopy(this.id, 0, copy, 0, this.id.length);
// determine index of byte and the value to be added
int indexOfByte = this.id.length - 1 - (powerOfTwo / 8);
byte[] toAdd = { 1, 2, 4, 8, 16, 32, 64, -128 };
byte valueToAdd = toAdd[powerOfTwo % 8];
byte oldValue;
do {
// add value
oldValue = copy[indexOfByte];
copy[indexOfByte] += valueToAdd;
// reset value to 1 for possible overflow situation
valueToAdd = 1;
}
// check for overflow - occurs if old value had a leading one, i.e. it
// was negative, and new value has a leading zero, i.e. it is zero or
// positive; indexOfByte >= 0 prevents running out of the array to the
// left in case of going over the maximum of the ID space
while (oldValue < 0 && copy[indexOfByte] >= 0 && indexOfByte-- > 0);
return new ID(copy);
}
/**
* 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 {
return this.toBigInteger().compareTo(otherKey.toBigInteger());
// kann man schon so machen, aber dann ist es halt kacke!
// if (this.getLength() != otherKey.getLength()) {
// throw new ClassCastException("Only ID objects with same length can be "
// + "compared! This ID is " + this.getLength()
// + " bits long while the other ID is "
// + otherKey.getLength() + " bits long.");
// }
//
// // compare value byte by byte
// byte[] otherBytes = new byte[this.id.length];
// System.arraycopy(otherKey.id, 0, otherBytes, 0, this.id.length);
//
// for (int i = 0; i < this.id.length; i++) {
// if ((byte) (this.id[i] - 128) < (byte) (otherBytes[i] - 128)) {
// return -1; // this ID is smaller
// } else if ((byte) (this.id[i] - 128) > (byte) (otherBytes[i] - 128)) {
// return 1; // this ID is greater
// }
// }
// return 0;
}
/**
* @see java.lang.Object#hashCode()
*/
public final int hashCode() {
int result = 19;
for (int i = 0; i < this.id.length; i++) {
result = 13 * result + this.id[i];
}
return result;
}
/**
* Checks if this ID is in the interval determined by the two given IDs.
* Neither of the boundary IDs is included in the interval. If both IDs
* match, the interval is assumed to span the whole ID ring.
*
* @param fromID
* Lower bound of interval.
* @param toID
* Upper bound of interval.
* @return If this key is included in the given interval.
*/
public final boolean isInInterval(ID fromID, ID toID) {
// both interval bounds are equal -> calculate out of equals
if (fromID.equals(toID)) {
// every ID is contained in the interval except of the two bounds
return (!this.equals(fromID));
}
// interval does not cross zero -> compare with both bounds
if (fromID.compareTo(toID) < 0) {
return (this.compareTo(fromID) > 0 && this.compareTo(toID) < 0);
}
// interval crosses zero -> split interval at zero
// calculate min and max IDs
byte[] minIDBytes = new byte[this.id.length];
ID minID = new ID(minIDBytes);
byte[] maxIDBytes = new byte[this.id.length];
for (int i = 0; i < maxIDBytes.length; i++) {
maxIDBytes[i] = -1;
}
ID maxID = new ID(maxIDBytes);
// check both splitted intervals
// first interval: (fromID, maxID]
return ((!fromID.equals(maxID) && this.compareTo(fromID) > 0 && this
.compareTo(maxID) <= 0) ||
// second interval: [minID, toID)
(!minID.equals(toID) && this.compareTo(minID) >= 0 && this
.compareTo(toID) < 0));
}
}