package uc.user;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import uc.DCClient;
import uc.IUser;
import uc.IUserChangedListener;
import uc.IUserChangedListener.UserChange;
import uc.IUserChangedListener.UserChangeEvent;
import uc.crypto.HashValue;
import uc.crypto.TigerHashValue;
import uc.files.filelist.FileListDescriptor;
import uc.protocols.hub.Hub;
/**
* Population on one side holds all online users (weak links)
* basically all users that are alive in some way in the system.
* So also offline users that may not be garbage collected from some reason..
*
*
* @author Quicksilver
*
*/
public final class Population {
private static Logger logger = LoggerFactory.make();
/**
* this field only contain weak references to the users..
* (users with DQEs won`t be removed then.. or otherwise referenced and therefore important )
*/
private final Map<HashValue,WeakReference<User>> allUsers =
Collections.synchronizedMap( new WeakHashMap<HashValue,WeakReference<User>>());
/**
* maps listeners to IDs of users..
* this is static so it we can map users that don`t exist currently
* additional it will cost less storage.. because the listeners
* will be sparely used..(one field less for the User-object)
*
*/
private final Map<HashValue,CopyOnWriteArrayList<IUserChangedListener>> idToUCListener =
Collections.synchronizedMap(new HashMap<HashValue,CopyOnWriteArrayList<IUserChangedListener>>());
private final CopyOnWriteArrayList<IUserChangedListener> ucListeners =
new CopyOnWriteArrayList<IUserChangedListener>();
//additional set for users that may not get deleted by garbage collector
private final Set<User> favs = new CopyOnWriteArraySet<User>();
private final Set<User> slotGranted = new CopyOnWriteArraySet<User>();
private volatile ScheduledFuture<?> slotGrantCheck;
private final DCClient dcc;
public Population(DCClient dcc) {
this.dcc = dcc;
}
/**
* delegate to the weak storage HashMap
*
* @param userid
* @return
*/
private User getUser(HashValue userid) {
WeakReference<User> userRef = allUsers.get(userid);
if (userRef != null) {
return userRef.get();
} else {
return null;
}
}
/**
* retrieves a user..that is either online
* or potentially retrieves a persisted
* user .. otherwise just
* creates a user-obj for nick and userid.
*
* @param nick - nick of the user
* @param userid - id of the user (in NMDC created from nick and hub address)
* @return a User possibly stored.. else created
*/
public User get(String nick, HashValue userid) {
synchronized (allUsers) {
User usr = getUser(userid);
if (usr == null) {
usr = new User(dcc,nick,userid);
allUsers.put(userid, new WeakReference<User>(usr));
}
return usr;
}
}
/**
* retrieve a user by his ID though
* @param userid
* @return user with given ID , or null if not present
*/
public IUser get(HashValue userid) {
return getUser(userid);
}
public Set<User> getFavsAndSlotGranted() {
refresh();
logger.debug("retrieving favs: "+favs.size());
Set<User> favsAndSlotranted = new HashSet<User>(favs);
favsAndSlotranted.addAll(slotGranted);
return favsAndSlotranted;
}
public Set<IUser> getSlotGranted() {
return Collections.<IUser>unmodifiableSet(slotGranted);
}
public Set<IUser> getFavUsers() {
return Collections.<IUser>unmodifiableSet(favs);
}
/**
* tries to remove users without current slot grants
* from the list
* periodically called for checking..
*/
private boolean refresh() {
boolean containsUserWithoutPermanentslot = false;
for (User u: slotGranted) {
if (!u.hasCurrentlyAutogrant()) {
u.notifyUserChanged(UserChange.CHANGED, UserChangeEvent.SLOTGRANT_REVOKED);
} else {
containsUserWithoutPermanentslot = containsUserWithoutPermanentslot || !u.isAutograntSlot();
}
}
return containsUserWithoutPermanentslot;
}
/**
* Settings.getStoragePath()+File.separator+"Filelists"+File.separator+usr.getNick()+"."+usr.getUserid()+".xml.bz2"
* @param path the file that points to the filelistfile..
* note the naming convention is used to find a user for this filelist
*
* username.userid.xml.bz2
*
* @return a User with a fileDescriptor pointing to the file
*/
public User getUserForFilelistfile(File path) {
logger.debug(path);
String name = path.getName();
User usr ;
Pattern p = Pattern.compile("(.*)\\.("+TigerHashValue.TTHREGEX+")\\.xml(?:.bz2)?");
Matcher m = p.matcher(name);
if (m.matches()) {
HashValue userid = HashValue.createHash(m.group(2));
usr = get(m.group(1),userid);
} else {
//not a legal FileList name found .. so we create an unknown user for the filelist
usr = get("unknown",TigerHashValue.ZEROBYTEHASH); //zero byte hash .. for the unknown user // splits.length > 1? splits[0]:
}
usr.setFilelistDescriptor(new FileListDescriptor(usr,path));
return usr;
}
/*
*
* @param favUser - if true user should
* be set to FavUser
* set
* if false removed..
* used by user so a persistent link to the user is created
* so he is not deleted
*
void addFavsAndSlotGranted(boolean add,User usr) {
if (add) {
if (favsAndSlotGranted.add(usr)) { //user may not be deleted by GC
notifyObservers(new StatusObject(usr,ChangeType.ADDED));
}
logger.debug("user set added to favset");
} else {
//no longer FavUsr -> AutoGrant was removed automatically..
if (favsAndSlotGranted.remove(usr)) {
notifyObservers(new StatusObject(usr,ChangeType.REMOVED));
}
}
} */
/**
* registers the provided listener to be notified when
* the state of the user changes
* @param listener - the listener to add
* multiple entries of the same listener will be ignored
*/
public void registerUserChangedListener(IUserChangedListener listener,HashValue userid) {
if (listener == null) {
throw new IllegalArgumentException("provided listener was null");
}
CopyOnWriteArrayList<IUserChangedListener> listeners = idToUCListener.get(userid);
if (listeners == null) {
listeners = new CopyOnWriteArrayList<IUserChangedListener>();
idToUCListener.put(userid, listeners);
}
listeners.addIfAbsent(listener);
}
/**
* registers the provided listener to be notified when
* the state of any user changes
* @param listener - the listener to add
* multiple entries of the same listener will be ignored
*/
public void registerUserChangedListener(IUserChangedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("provided listener was null");
}
ucListeners.addIfAbsent(listener);
}
/**
* removes the provided listener so he will no longer be notified
* @param listener - the listener to be removed.
*/
public void unregisterUserChangedListener(IUserChangedListener listener,HashValue userid) {
if (listener == null) {
throw new IllegalArgumentException("provided listener was null");
}
CopyOnWriteArrayList<IUserChangedListener> listeners = idToUCListener.get(userid);
if (listeners != null) {
listeners.remove(listener);
if (listeners.isEmpty()) {
idToUCListener.remove(userid);
}
}
}
/**
* removes the provided listener so he will no longer be notified
* @param listener - the listener to be removed.
*/
public void unregisterUserChangedListener(IUserChangedListener listener) {
if (listener == null) {
throw new IllegalArgumentException("provided listener was null");
}
ucListeners.remove(listener);
}
/**
* called by user to notify listeners of a userchange
* nobody else may call..
*/
void internal_userChanged(User usr,UserChange type,int detail) {
switch(detail) {
case UserChangeEvent.FAVUSER_ADDED:
favs.add(usr);
break;
case UserChangeEvent.FAVUSER_REMOVED:
favs.remove(usr);
break;
case UserChangeEvent.SLOT_GRANTED:
slotGranted.add(usr);
if (slotGrantCheck == null && refresh()) {
slotGrantCheck = dcc.getSchedulerDir().schedule(new Runnable() {
/**
* checks every 60 seconds while a user wit non permanent slot
* is present for changes...
*/
public void run() {
if (refresh()) {
slotGrantCheck = dcc.getSchedulerDir().schedule(this, 60, TimeUnit.SECONDS);
} else {
slotGrantCheck = null;
}
}
}, 60, TimeUnit.SECONDS);
}
break;
case UserChangeEvent.SLOTGRANT_REVOKED:
slotGranted.remove(usr);
break;
}
UserChangeEvent uce = new UserChangeEvent(usr, type,detail);
List<IUserChangedListener> listeners = idToUCListener.get(usr.getUserid());
if (listeners != null) {
for (IUserChangedListener ucl: listeners) {
ucl.changed(uce);
}
}
for (IUserChangedListener ucl: ucListeners) {
ucl.changed(uce);
}
Hub h = usr.getHub();
if (h != null) {
h.internal_notifyUserChanged(uce);
}
}
}