package com.limegroup.gnutella.altlocs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.listener.EventListener;
import com.google.inject.Singleton;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.library.FileViewChangeEvent;
@Singleton
public class AltLocManager implements EventListener<FileViewChangeEvent> {
private static final Log LOG = LogFactory.getLog(AltLocManager.class);
/**
* Map of the alternate location collections for each URN.
* LOCKING: itself for all map operations as well as operations on the contained arrays
*/
private final Map<URN, URNData> urnMap = Collections.synchronizedMap(new HashMap<URN, URNData>());
/**
* Adds a given altloc to the manager.
* @return whether the manager already knew about this altloc
*/
public boolean add(AlternateLocation al, Object source) {
if (LOG.isTraceEnabled()) {
LOG.trace("alternate location added: " + al);
}
URN sha1 = al.getSHA1Urn();
AlternateLocationCollection<DirectAltLoc> dCol = null;
AlternateLocationCollection<PushAltLoc> pCol = null;
URNData data;
synchronized(urnMap) {
data = 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);
dCol = data.direct;
} else if(al instanceof PushAltLoc) {
PushAltLoc push = (PushAltLoc) al;
if (push.supportsFWTVersion() < 1) {
if (data.push == AlternateLocationCollection.EMPTY)
data.push = AlternateLocationCollection.create(sha1);
pCol = data.push;
} else {
if (data.fwt == AlternateLocationCollection.EMPTY)
data.fwt = AlternateLocationCollection.create(sha1);
pCol = data.fwt;
}
} else {
throw new IllegalStateException("unknown loc class: " + al.getClass());
}
}
boolean ret = false;
if(dCol != null) {
ret = dCol.add((DirectAltLoc)al);
} else if(pCol != null) {
ret = pCol.add((PushAltLoc)al);
} else {
throw new IllegalStateException("didn't set a collection!");
}
// notify any listeners other than the source
for(AltLocListener listener : data.getListeners()) {
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 = urnMap.get(sha1);
if (data == null)
return false;
AlternateLocationCollection<DirectAltLoc> dCol = null;
AlternateLocationCollection<PushAltLoc> pCol = null;
synchronized(data) {
if (al instanceof DirectAltLoc) {
dCol = data.direct;
} else {
PushAltLoc push = (PushAltLoc) al;
if (push.supportsFWTVersion() < 1)
pCol = data.push;
else
pCol = data.fwt;
}
}
AlternateLocationCollection col = null;
boolean ret = false;
if(dCol != null) {
ret = dCol.remove((DirectAltLoc)al);
col = dCol;
} else if(pCol != null) {
ret = pCol.remove((PushAltLoc)al);
col = pCol;
} else {
return false;
}
// 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
*/
public AlternateLocationCollection<DirectAltLoc> getDirect(URN sha1) {
URNData data = urnMap.get(sha1);
if (data == null)
return AlternateLocationCollection.getEmptyCollection();
synchronized(data) {
return data.direct;
}
}
/**
* Returns push alternate locations that do not support FWT.
* @param sha1 the URN for which to get altlocs
*/
public AlternateLocationCollection<PushAltLoc> getPushNoFWT(URN sha1) {
URNData data = urnMap.get(sha1);
if (data == null)
return AlternateLocationCollection.getEmptyCollection();
synchronized(data) {
return data.push;
}
}
/**
* Returns push alternate locations that support FWT.
* @param sha1 the URN for which to get altlocs
*/
public AlternateLocationCollection<PushAltLoc> getPushFWT(URN sha1) {
URNData data = urnMap.get(sha1);
if (data == null)
return AlternateLocationCollection.getEmptyCollection();
synchronized(data) {
return data.fwt;
}
}
public void purge(){
urnMap.clear();
}
private void purge(URN sha1) {
urnMap.remove(sha1);
}
public boolean hasAltlocs(URN sha1) {
URNData data = urnMap.get(sha1);
if (data == null)
return false;
return data.hasAltLocs();
}
public int getNumLocs(URN sha1) {
URNData data = urnMap.get(sha1);
if (data == null)
return 0;
return data.getNumLocs();
}
public void addListener(URN sha1, AltLocListener listener) {
URNData data;
synchronized(urnMap){
data = 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 = urnMap.get(sha1);
if (data == null)
return;
data.removeListener(listener);
removeIfEmpty(sha1,data);
}
/**
* Listens for events from FileManager.
*/
public void handleEvent(FileViewChangeEvent evt) {
switch(evt.getType()) {
case FILES_CLEARED:
purge();
break;
case FILE_REMOVED:
URN urn = evt.getFileDesc().getSHA1Urn();
// Purge if there's no more FDs for this URN.
if(urn != null && evt.getFileView().getFileDescsMatching(urn).isEmpty()) {
purge(urn);
}
break;
}
}
private static class URNData {
/**
* The three alternate locations we keep with this urn.
* LOCKING: this
*/
public AlternateLocationCollection<DirectAltLoc> direct = AlternateLocationCollection.getEmptyCollection();
public AlternateLocationCollection<PushAltLoc> push = AlternateLocationCollection.getEmptyCollection();
public AlternateLocationCollection<PushAltLoc> fwt = AlternateLocationCollection.getEmptyCollection();
private volatile List<AltLocListener> listeners = Collections.emptyList();
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<AltLocListener> updated = new ArrayList<AltLocListener>(listeners);
updated.add(listener);
listeners = updated;
}
public synchronized void removeListener(AltLocListener listener) {
List<AltLocListener> updated = new ArrayList<AltLocListener>(listeners);
updated.remove(listener);
listeners = updated;
}
public List<AltLocListener> getListeners() {
return listeners;
}
}
}