package com.limegroup.gnutella.altlocs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.limegroup.gnutella.URN;
public class AltLocManager {
private static final AltLocManager INSTANCE = new AltLocManager();
public static AltLocManager instance() {
return INSTANCE;
}
/**
* Mapping of URN - > array of three alternate locations.
* LOCKING: itself for all map operations as well as operations on the contained arrays
*/
private final Map urnMap = Collections.synchronizedMap(new HashMap());
private AltLocManager() {}
/**
* adds a given altloc to the manager
* @return whether the manager already knew about this altloc
*/
public boolean add(AlternateLocation al, Object source) {
URN sha1 = al.getSHA1Urn();
AlternateLocationCollection col = null;
URNData data;
synchronized(urnMap) {
data = (URNData) urnMap.get(sha1);
if (data == null) {
data = new URNData();
urnMap.put(sha1,data);
}
}
synchronized(data) {
if (al instanceof DirectAltLoc) {
if (data.direct == AlternateLocationCollection.EMPTY)
data.direct = AlternateLocationCollection.create(sha1);
col = data.direct;
}
else {
PushAltLoc push = (PushAltLoc) al;
if (push.supportsFWTVersion() < 1) {
if (data.push == AlternateLocationCollection.EMPTY)
data.push = AlternateLocationCollection.create(sha1);
col = data.push;
} else {
if (data.fwt == AlternateLocationCollection.EMPTY)
data.fwt = AlternateLocationCollection.create(sha1);
col = data.fwt;
}
}
}
boolean ret = col.add(al);
// notify any listeners other than the source
for (Iterator iter = data.getListeners().iterator(); iter.hasNext();) {
AltLocListener listener = (AltLocListener) iter.next();
if (listener == source)
continue;
listener.locationAdded(al);
}
return ret;
}
/**
* removes the given altloc (implementations may demote)
*/
public boolean remove(AlternateLocation al, Object source) {
URN sha1 = al.getSHA1Urn();
URNData data = (URNData) urnMap.get(sha1);
if (data == null)
return false;
AlternateLocationCollection col;
synchronized(data) {
if (al instanceof DirectAltLoc)
col = data.direct;
else {
PushAltLoc push = (PushAltLoc) al;
if (push.supportsFWTVersion() < 1)
col = data.push;
else
col = data.fwt;
}
}
if (col == null)
return false;
boolean ret = col.remove(al);
// if we emptied the current collection, see if the rest are empty as well
if (!col.hasAlternateLocations())
removeIfEmpty(sha1,data);
return ret;
}
private void removeIfEmpty(URN sha1, URNData data) {
boolean empty = false;
synchronized(data) {
if (!data.direct.hasAlternateLocations() &&
!data.push.hasAlternateLocations() &&
!data.fwt.hasAlternateLocations() &&
data.getListeners().isEmpty())
empty = true;
}
if (empty)
urnMap.remove(sha1);
}
/**
* @param sha1 the URN for which to get altlocs
* @param size the maximum number of altlocs to return
*/
public AlternateLocationCollection getDirect(URN sha1) {
URNData data = (URNData) urnMap.get(sha1);
if (data == null)
return AlternateLocationCollection.EMPTY;
synchronized(data) {
return data.direct;
}
}
/**
* @param sha1 the URN for which to get altlocs
* @param size the maximum number of altlocs to return
* @param FWTOnly whether the altlocs must support FWT
*/
public AlternateLocationCollection getPush(URN sha1, boolean FWTOnly) {
URNData data = (URNData) urnMap.get(sha1);
if (data == null)
return AlternateLocationCollection.EMPTY;
synchronized(data) {
return FWTOnly ? data.fwt : data.push;
}
}
public void purge(){
urnMap.clear();
}
public void purge(URN sha1) {
urnMap.remove(sha1);
}
public boolean hasAltlocs(URN sha1) {
URNData data = (URNData) urnMap.get(sha1);
if (data == null)
return false;
return data.hasAltLocs();
}
public int getNumLocs(URN sha1) {
URNData data = (URNData) urnMap.get(sha1);
if (data == null)
return 0;
return data.getNumLocs();
}
public void addListener(URN sha1, AltLocListener listener) {
URNData data;
synchronized(urnMap){
data = (URNData) urnMap.get(sha1);
if (data == null) {
data = new URNData();
urnMap.put(sha1,data);
}
}
data.addListener(listener);
}
public void removeListener(URN sha1, AltLocListener listener) {
URNData data = (URNData) urnMap.get(sha1);
if (data == null)
return;
data.removeListener(listener);
removeIfEmpty(sha1,data);
}
private static class URNData {
/**
* The three alternate locations we keep with this urn.
* LOCKING: this
*/
public AlternateLocationCollection direct = AlternateLocationCollection.EMPTY;
public AlternateLocationCollection push = AlternateLocationCollection.EMPTY;
public AlternateLocationCollection fwt = AlternateLocationCollection.EMPTY;
private volatile List listeners = Collections.EMPTY_LIST;
public synchronized boolean hasAltLocs() {
return direct.hasAlternateLocations() ||
push.hasAlternateLocations() ||
fwt.hasAlternateLocations();
}
public synchronized int getNumLocs() {
return direct.getAltLocsSize() + push.getAltLocsSize() + fwt.getAltLocsSize();
}
public synchronized void addListener(AltLocListener listener) {
List updated = new ArrayList(listeners);
updated.add(listener);
listeners = updated;
}
public synchronized void removeListener(AltLocListener listener) {
List updated = new ArrayList(listeners);
updated.remove(listener);
listeners = updated;
}
public List getListeners() {
return listeners;
}
}
}