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); } } } }