package com.limegroup.gnutella;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.limegroup.gnutella.messages.PingReply;
import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.util.BucketQueue;
/**
* This class caches pongs from the network. Caching pongs saves considerable
* bandwidth because only a controlled number of pings are sent to maintain
* adequate host data, with Ultrapeers caching and responding to pings with
* the best pongs available.
*/
public final class PongCacher {
/**
* Single <tt>PongCacher</tt> instance, following the singleton pattern.
*/
private static final PongCacher INSTANCE = new PongCacher();
/**
* Constant for the number of pongs to store per hop. Public to make
* testing easier.
*/
public static final int NUM_PONGS_PER_HOP = 1;
/**
* Constant for the number of hops to keep track of in our pong cache.
*/
public static final int NUM_HOPS = 6;
/**
* Constant for the number of seconds to wait before expiring cached pongs.
*/
public static final int EXPIRE_TIME = 6000;
/**
* Constant for expiring locale specific pongs
*/
public static final int EXPIRE_TIME_LOC = 15*EXPIRE_TIME;
/**
* <tt>BucketQueue</tt> holding pongs separated by hops.
* The map is of String (locale) to BucketQueue (Pongs per Hop)
*/
private static final Map /* String -> BucketQueue */ PONGS = new HashMap();
/**
* Returns the single <tt>PongCacher</tt> instance.
*/
public static PongCacher instance() {
return INSTANCE;
}
/**
* Private constructor to ensure only one instance is created.
*/
private PongCacher() {}
/**
* Accessor for the <tt>Set</tt> of cached pongs. This <tt>List</tt>
* is unmodifiable and will throw <tt>IllegalOperationException</tt> if
* it is modified.
*
* @return the <tt>List</tt> of cached pongs -- continually updated
*/
public List getBestPongs(String loc) {
synchronized(PONGS) {
List pongs = new LinkedList(); //list to return
long curTime = System.currentTimeMillis();
//first we try to populate "pongs" with those pongs
//that match the locale
List removeList =
addBestPongs(loc, pongs, curTime, 0);
//remove all stale pongs that were reported for the
//locale
removePongs(loc, removeList);
//if the locale that we were searching for was not the default
//"en" locale and we do not have enough pongs in the list
//then populate the list "pongs" with the default locale pongs
if(!ApplicationSettings.DEFAULT_LOCALE.getValue().equals(loc)
&& pongs.size() < NUM_HOPS) {
//get the best pongs for default locale
removeList =
addBestPongs(ApplicationSettings.DEFAULT_LOCALE.getValue(),
pongs,
curTime,
pongs.size());
//remove any pongs that were reported as stale pongs
removePongs(ApplicationSettings.DEFAULT_LOCALE.getValue(),
removeList);
}
return pongs;
}
}
/**
* adds good pongs to the passed in list "pongs" and
* return a list of pongs that should be removed.
*/
private List addBestPongs(String loc, List pongs,
long curTime, int i) {
//set the expire time to be used.
//if the locale that is passed in is "en" then just use the
//normal expire time otherwise use the longer expire time
//so we can have some memory of non english locales
int exp_time =
(ApplicationSettings.DEFAULT_LOCALE.getValue().equals(loc))?
EXPIRE_TIME :
EXPIRE_TIME_LOC;
//check if there are any pongs of the specific locale stored
//in PONGS.
List remove = null;
if(PONGS.containsKey(loc)) {
//get all the pongs that are of the specific locale and
//make sure that they are not stale
BucketQueue bq = (BucketQueue)PONGS.get(loc);
Iterator iter = bq.iterator();
for(;iter.hasNext() && i < NUM_HOPS; i++) {
PingReply pr = (PingReply)iter.next();
//if the pongs are stale put into the remove list
//to be returned. Didn't pass in the remove list
//into this function because we may never see stale
//pongs so we won't need to new a linkedlist
//this may be a premature and unnecessary opt.
if(curTime - pr.getCreationTime() > exp_time) {
if(remove == null)
remove = new LinkedList();
remove.add(pr);
}
else {
pongs.add(pr);
}
}
}
return remove;
}
/**
* removes the pongs with the specified locale and those
* that are in the passed in list l
*/
private void removePongs(String loc, List l) {
if(l != null) {
BucketQueue bq = (BucketQueue)PONGS.get(loc);
Iterator iter = l.iterator();
while(iter.hasNext()) {
PingReply pr = (PingReply)iter.next();
bq.removeAll(pr);
}
}
}
/**
* Adds the specified <tt>PingReply</tt> instance to the cache of pongs.
*
* @param pr the <tt>PingReply</tt> to add
*/
public void addPong(PingReply pr) {
// if we're not an Ultrapeer, we don't care about caching the pong
if(!RouterService.isSupernode()) return;
// Make sure we don't cache pongs that aren't from Ultrapeers.
if(!pr.isUltrapeer()) return;
// if the hops are too high, ignore it
if(pr.getHops() >= NUM_HOPS) return;
synchronized(PONGS) {
//check the map for the locale and create or retrieve the set
if(PONGS.containsKey(pr.getClientLocale())) {
BucketQueue bq = (BucketQueue)PONGS.get(pr.getClientLocale());
bq.insert(pr, pr.getHops());
}
else {
BucketQueue bq = new BucketQueue(NUM_HOPS, NUM_PONGS_PER_HOP);
bq.insert(pr, pr.getHops());
PONGS.put(pr.getClientLocale(), bq);
}
}
}
}