package freenet.node;
import java.util.Arrays;
import java.util.Set;
import freenet.support.Logger;
public class PeerLocation {
/** Current location in the keyspace, or -1 if it is unknown */
private double currentLocation;
/** Current sorted array of locations of our peer's peers. Must not be modified,
may only be replaced entirely. */
private double[] currentPeersLocation;
/** Time the location was set */
private long locSetTime;
PeerLocation(String locationString) {
currentLocation = Location.getLocation(locationString);
locSetTime = System.currentTimeMillis();
}
public synchronized String toString() {
return Double.toString(currentLocation);
}
/** Should only be called in the constructor */
public void setPeerLocations(String[] peerLocationsString) {
if(peerLocationsString != null) {
double[] peerLocations = new double[peerLocationsString.length];
for(int i = 0; i < peerLocationsString.length; i++)
peerLocations[i] = Location.getLocation(peerLocationsString[i]);
updateLocation(currentLocation, peerLocations);
}
}
public synchronized double getLocation() {
return currentLocation;
}
/** Returns an array copy of locations of our peer's peers, or null if we don't have them. */
public synchronized double[] getPeersLocationArray() {
if (currentPeersLocation == null) {
return null;
}
return Arrays.copyOf(currentPeersLocation, currentPeersLocation.length);
}
public synchronized long getLocationSetTime() {
return locSetTime;
}
public synchronized boolean isValidLocation() {
return Location.isValid(currentLocation);
}
public synchronized int getDegree() {
if (currentPeersLocation == null) return 0;
return currentPeersLocation.length;
}
boolean updateLocation(double newLoc, double[] newLocs) {
if (!Location.isValid(newLoc)) {
Logger.error(this, "Invalid location update for " + this+ " ("+newLoc+')', new Exception("error"));
// Ignore it
return false;
}
final double[] newPeersLocation = new double[newLocs.length];
for (int i = 0; i < newLocs.length; i++) {
final double loc = newLocs[i];
if (!Location.isValid(loc)) {
Logger.error(this, "Invalid location update for " + this + " (" + loc + ")", new Exception("error"));
// Ignore it
return false;
}
newPeersLocation[i] = loc;
}
Arrays.sort(newPeersLocation);
boolean anythingChanged = false;
synchronized (this) {
if (!Location.equals(currentLocation, newLoc) || currentPeersLocation == null) {
anythingChanged = true;
}
if (!anythingChanged) {
anythingChanged = currentPeersLocation.length != newPeersLocation.length;
}
if (!anythingChanged) {
for (int i = 0; i < currentPeersLocation.length; i++) {
if (!Location.equals(currentPeersLocation[i], newPeersLocation[i])) {
anythingChanged = true;
break;
}
}
}
currentLocation = newLoc;
currentPeersLocation = newPeersLocation;
locSetTime = System.currentTimeMillis();
}
return anythingChanged;
}
synchronized double setLocation(double newLoc) {
double oldLoc = currentLocation;
if(!Location.equals(newLoc, currentLocation)) {
currentLocation = newLoc;
locSetTime = System.currentTimeMillis();
}
return oldLoc;
}
/**
* Finds the position of the first element in the sorted list greater than the given element,
* or -1 if none.
* This is a binary search of logarithmic complexity.
*/
private static int findFirstGreater(final double[] elems, final double x) {
int low = 0;
int high = elems.length;
int mid;
if (elems[high - 1] <= x) {
return -1;
}
while (low != high) {
mid = low + (high - low) / 2;
if (elems[mid] > x) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
/**
* Finds the position of the closest location in the sorted list of locations.
* This is a binary search of logarithmic complexity.
*/
static int findClosestLocation(final double[] locs, final double l) {
assert(locs.length > 0);
if (locs.length == 1) {
return 0;
}
final int firstGreater = findFirstGreater(locs, l);
final int left;
final int right;
if (firstGreater == -1 || firstGreater == 0) {
// All locations are greater, or all locations are smaller.
// Closest location must be either smallest or greatest location.
left = locs.length - 1;
right = 0;
} else {
left = firstGreater - 1;
right = firstGreater;
}
if (Location.distance(l, locs[left]) <= Location.distance(l, locs[right])) {
return left;
}
return right;
}
/**
* Finds the closest non-excluded peer in O(log n + m) time, where n is the number of peers and
* m the number of exclusions.
* @param exclude the set of locations to exclude, may be null
* @return the closest non-excluded peer's location, or NaN if none is found
*/
public double getClosestPeerLocation(final double l, Set<Double> exclude) {
final double[] locs;
synchronized (this) {
locs = currentPeersLocation;
}
if (locs == null || locs.length == 0) {
return Double.NaN;
}
final int closest = findClosestLocation(locs, l);
if (exclude == null || !exclude.contains(locs[closest])) {
return locs[closest];
}
int left = (closest == 0) ? (locs.length - 1) : (closest - 1);
int right = (closest == locs.length - 1) ? 0 : (closest + 1);
double leftDist = Location.distance(l, locs[left]);
double rightDist = Location.distance(l, locs[right]);
// Iterate over at most m closest peers
while (left != right) {
if (leftDist <= rightDist) {
final double loc = locs[left];
if (!exclude.contains(loc)) {
return loc;
}
left = (left == 0) ? (locs.length - 1) : (left - 1);
leftDist = Location.distance(l, locs[left]);
} else {
final double loc = locs[right];
if (!exclude.contains(loc)) {
return loc;
}
right = (right == locs.length - 1) ? 0 : (right + 1);
rightDist = Location.distance(l, locs[right]);
}
}
final double loc = locs[left];
if (!exclude.contains(loc)) {
return loc;
}
return Double.NaN;
}
}