package org.limewire.core.impl.library;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.limewire.collection.MultiIterable;
import org.limewire.collection.glazedlists.GlazedListsFactory;
import org.limewire.core.api.library.FriendLibrary;
import org.limewire.core.api.library.PresenceLibrary;
import org.limewire.core.api.library.RemoteLibrary;
import org.limewire.core.api.library.RemoteLibraryEvent;
import org.limewire.core.api.library.RemoteLibraryManager;
import org.limewire.core.api.library.RemoteLibraryState;
import org.limewire.core.api.search.SearchResult;
import org.limewire.friend.api.Friend;
import org.limewire.friend.api.FriendPresence;
import org.limewire.listener.EventListener;
import org.limewire.listener.EventListenerList;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.util.StringUtils;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.impl.ReadOnlyList;
import ca.odell.glazedlists.util.concurrent.LockFactory;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* This class keeps track of all friends libraries. As friend presences are found they are
* aggregated into a single friend library per friend. All RemoteFileItems found in the friend libraries
* are also coalesced into a single FileList.
*/
@Singleton
public class RemoteLibraryManagerImpl implements RemoteLibraryManager {
private static final Log LOG = LogFactory.getLog(RemoteLibraryManagerImpl.class, "friend-library");
private final EventList<FriendLibrary> allFriendLibraries;
private final EventListenerList<RemoteLibraryEvent> listeners = new EventListenerList<RemoteLibraryEvent>();
private final AllFriendsLibraryImpl allFriendsLibrary = new AllFriendsLibraryImpl();
private final PropagatingEventListener propagatingEventListener = new PropagatingEventListener(allFriendsLibrary, listeners);
/**
* Common lock for all glazed lists in here.
*/
private final ReadWriteLock listLock = LockFactory.DEFAULT.createReadWriteLock();
@Inject
public RemoteLibraryManagerImpl() {
allFriendLibraries = GlazedListsFactory.threadSafeList(new BasicEventList<FriendLibrary>(listLock));
}
@Override
public RemoteLibrary getAllFriendsLibrary() {
return allFriendsLibrary;
}
@Override
public EventList<FriendLibrary> getFriendLibraryList() {
return allFriendLibraries;
}
@Override
public boolean addPresenceLibrary(FriendPresence presence) {
assert !presence.getFriend().isAnonymous();
listLock.writeLock().lock();
try {
FriendLibraryImpl friendLibrary = getOrCreateFriendLibrary(presence.getFriend());
return friendLibrary.addPresenceLibrary(presence);
} finally {
listLock.writeLock().unlock();
}
}
@Override
public PresenceLibrary getPresenceLibrary(FriendPresence presence) {
listLock.readLock().lock();
try {
FriendLibraryImpl friendLibrary = getFriendLibrary(presence.getFriend());
if(friendLibrary != null) {
return friendLibrary.getPresenceLibrary(presence);
} else {
return null;
}
} finally {
listLock.readLock().unlock();
}
}
private void removeFriendLibrary(FriendLibraryImpl friendLibrary) {
LOG.debugf("removing friend library for {0}", friendLibrary.getFriend());
listLock.writeLock().lock();
try {
friendLibrary.removeListener(propagatingEventListener);
allFriendLibraries.remove(friendLibrary);
} finally {
listLock.writeLock().unlock();
}
}
@Override
public void removePresenceLibrary(FriendPresence presence) {
listLock.writeLock().lock();
try {
FriendLibraryImpl friendLibrary = getFriendLibrary(presence.getFriend());
if (friendLibrary != null) {
friendLibrary.removePresenceLibrary(presence);
if (friendLibrary.getPresenceLibraryList().isEmpty()) {
removeFriendLibrary(friendLibrary);
}
}
} finally {
listLock.writeLock().unlock();
}
}
private FriendLibraryImpl getOrCreateFriendLibrary(Friend friend) {
listLock.writeLock().lock();
try {
FriendLibraryImpl friendLibrary = getFriendLibrary(friend);
if(friendLibrary == null) {
LOG.debugf("adding friend library for {0}", friend);
friendLibrary = new FriendLibraryImpl(friend);
friendLibrary.addListener(propagatingEventListener);
allFriendLibraries.add(friendLibrary);
}
return friendLibrary;
} finally {
listLock.writeLock().unlock();
}
}
@Override
public boolean hasFriendLibrary(Friend friend) {
return getFriendLibrary(friend) != null;
}
@Override
public FriendLibraryImpl getFriendLibrary(Friend friend) {
listLock.readLock().lock();
try {
for(FriendLibrary library : allFriendLibraries) {
if(library.getFriend().getId().equals(friend.getId())) {
return (FriendLibraryImpl)library;
}
}
return null;
} finally {
listLock.readLock().unlock();
}
}
private class AllFriendsLibraryImpl implements ParentRemoteLibrary {
private volatile RemoteLibraryState state = RemoteLibraryState.LOADING;
@Override
public int size() {
listLock.readLock().lock();
try {
int sum = 0;
for (RemoteLibrary remoteLibrary : allFriendLibraries) {
sum += remoteLibrary.size();
}
return sum;
} finally {
listLock.readLock().unlock();
}
}
public void addNewResult(SearchResult file) {
throw new UnsupportedOperationException();
}
@Override
public void setNewResults(Collection<SearchResult> files) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public RemoteLibraryState getState() {
return state;
}
public void setState(RemoteLibraryState state) {
this.state = state;
listeners.broadcast(RemoteLibraryEvent.createStateChangedEvent(this));
}
@Override
public Iterator<SearchResult> iterator() {
return new MultiIterable<SearchResult>(allFriendLibraries.toArray(new RemoteLibrary[0])).iterator();
}
@Override
public void addListener(EventListener<RemoteLibraryEvent> listener) {
listeners.addListener(listener);
}
@Override
public boolean removeListener(EventListener<RemoteLibraryEvent> listener) {
return listeners.removeListener(listener);
}
@Override
public void updateState() {
setState(calculateState(allFriendLibraries));
}
}
private class FriendLibraryImpl implements FriendLibrary, ParentRemoteLibrary {
private final Friend friend;
private final EventList<PresenceLibrary> allPresenceLibraries;
private final ReadOnlyList<PresenceLibrary> readOnlyPresenceLibraries;
private volatile RemoteLibraryState state = RemoteLibraryState.LOADING;
private final EventListenerList<RemoteLibraryEvent> listeners = new EventListenerList<RemoteLibraryEvent>();
// note: this call is exposing this in constructor, but is locally confined
private final PropagatingEventListener propagatingEventListener = new PropagatingEventListener(this, listeners);
public FriendLibraryImpl(Friend friend) {
this.friend = friend;
allPresenceLibraries = GlazedListsFactory.threadSafeList(new BasicEventList<PresenceLibrary>(listLock));
readOnlyPresenceLibraries = GlazedListsFactory.readOnlyList(allPresenceLibraries);
}
@Override
public EventList<PresenceLibrary> getPresenceLibraryList() {
return readOnlyPresenceLibraries;
}
@Override
public RemoteLibraryState getState() {
return state;
}
private void setState(RemoteLibraryState state) {
this.state = state;
listeners.broadcast(RemoteLibraryEvent.createStateChangedEvent(this));
}
public void updateState() {
setState(calculateState(allPresenceLibraries));
}
public Friend getFriend() {
return friend;
}
private PresenceLibraryImpl getPresenceLibrary(FriendPresence presence) {
listLock.readLock().lock();
try {
for(PresenceLibrary library : allPresenceLibraries) {
if(library.getPresence().getPresenceId().equals(presence.getPresenceId())) {
return (PresenceLibraryImpl)library;
}
}
return null;
} finally {
listLock.readLock().unlock();
}
}
private boolean addPresenceLibrary(FriendPresence presence) {
listLock.writeLock().lock();
try {
PresenceLibraryImpl library = getPresenceLibrary(presence);
if(library == null) {
LOG.debugf("adding presence library for {0}", presence);
library = new PresenceLibraryImpl(presence);
allPresenceLibraries.add(library);
library.addListener(propagatingEventListener);
return true;
} else {
return false;
}
} finally {
listLock.writeLock().unlock();
}
}
private void removePresenceLibrary(FriendPresence presence) {
listLock.writeLock().lock();
try {
PresenceLibraryImpl presenceLibrary = getPresenceLibrary(presence);
if(presenceLibrary != null) {
LOG.debugf("removing presence library for {0}", presence);
presenceLibrary.removeListener(propagatingEventListener);
allPresenceLibraries.remove(presenceLibrary);
}
} finally {
listLock.writeLock().unlock();
}
}
@Override
public int size() {
listLock.readLock().lock();
try {
int sum = 0;
for (PresenceLibrary presenceLibrary : readOnlyPresenceLibraries) {
sum += presenceLibrary.size();
}
return sum;
} finally {
listLock.readLock().unlock();
}
}
@Override
public void addNewResult(SearchResult file) {
throw new UnsupportedOperationException();
}
@Override
public void setNewResults(Collection<SearchResult> file) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return StringUtils.toString(this);
}
@Override
public Iterator<SearchResult> iterator() {
return new MultiIterable<SearchResult>(readOnlyPresenceLibraries.toArray(new RemoteLibrary[0])).iterator();
}
@Override
public void addListener(EventListener<RemoteLibraryEvent> listener) {
listeners.addListener(listener);
}
@Override
public boolean removeListener(EventListener<RemoteLibraryEvent> listener) {
return listeners.removeListener(listener);
}
}
static class PresenceLibraryImpl implements PresenceLibrary {
private final FriendPresence presence;
private volatile RemoteLibraryState state = RemoteLibraryState.LOADING;
private final List<SearchResult> results = Collections.synchronizedList(new ArrayList<SearchResult>());
private final EventListenerList<RemoteLibraryEvent> listeners = new EventListenerList<RemoteLibraryEvent>();
private final List<AddOnlyListIterator> iterators = new ArrayList<AddOnlyListIterator>(2);
PresenceLibraryImpl(FriendPresence presence) {
this.presence = presence;
}
@Override
public String toString() {
return StringUtils.toString(this, presence);
}
public FriendPresence getPresence() {
return presence;
}
@Override
public void addNewResult(SearchResult file) {
int startIndex;
synchronized (results) {
startIndex = results.size();
results.add(file);
}
listeners.broadcast(RemoteLibraryEvent.createResultsAddedEvent(this, Collections.singleton(file), startIndex));
}
@Override
public void setNewResults(Collection<SearchResult> files) {
clear();
int startIndex;
synchronized (results) {
startIndex = results.size();
results.addAll(files);
}
listeners.broadcast(RemoteLibraryEvent.createResultsAddedEvent(this, files, startIndex));
}
@Override
public void clear() {
synchronized (results) {
results.clear();
for (AddOnlyListIterator iterator : iterators) {
iterator.cleared = true;
}
iterators.clear();
}
listeners.broadcast(RemoteLibraryEvent.createResultsClearedEvent(this));
}
@Override
public int size() {
return results.size();
}
@Override
public RemoteLibraryState getState() {
return state;
}
@Override
public void setState(RemoteLibraryState newState) {
this.state = newState;
listeners.broadcast(RemoteLibraryEvent.createStateChangedEvent(this));
}
@Override
public Iterator<SearchResult> iterator() {
synchronized (results) {
AddOnlyListIterator iterator = new AddOnlyListIterator();
iterators.add(iterator);
return iterator;
}
}
private class AddOnlyListIterator implements Iterator<SearchResult> {
private int currentIndex = 0;
private boolean cleared = false;
private SearchResult next = null;
public AddOnlyListIterator() {
setNext();
}
private void setNext() {
synchronized (results) {
next = currentIndex < results.size() ? results.get(currentIndex) : null;
++currentIndex;
}
}
@Override
public boolean hasNext() {
if (cleared) {
return false;
}
return next != null;
}
@Override
public SearchResult next() {
SearchResult result = next;
setNext();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
@Override
public void addListener(EventListener<RemoteLibraryEvent> listener) {
listeners.addListener(listener);
}
@Override
public boolean removeListener(EventListener<RemoteLibraryEvent> listener) {
return listeners.removeListener(listener);
}
@Override
public SearchResult get(int index) {
return results.get(index);
}
}
static RemoteLibraryState calculateState(EventList<? extends RemoteLibrary> children) {
children.getReadWriteLock().readLock().lock();
try {
boolean oneCompleted = false;
for(RemoteLibrary library : children) {
switch (library.getState()) {
case LOADING:
return RemoteLibraryState.LOADING;
case LOADED:
oneCompleted = true;
break;
}
}
if(oneCompleted) {
return RemoteLibraryState.LOADED;
} else {
return RemoteLibraryState.FAILED_TO_LOAD;
}
} finally {
children.getReadWriteLock().readLock().unlock();
}
}
/**
* Helper interface used by {@link PropagatingEventListener} and
* implemented by remote libraries that contain other remote libraries.
*/
static interface ParentRemoteLibrary extends RemoteLibrary {
/**
* Called when the state of one of the contained remote libraries
* changed to allow the parent to recalculate its compound state.
*/
void updateState();
}
static class PropagatingEventListener implements EventListener<RemoteLibraryEvent> {
private final ParentRemoteLibrary parent;
private final EventListenerList<RemoteLibraryEvent> listeners;
public PropagatingEventListener(ParentRemoteLibrary parent, EventListenerList<RemoteLibraryEvent> listeners) {
this.parent = parent;
this.listeners = listeners;
}
@Override
public void handleEvent(RemoteLibraryEvent event) {
switch (event.getType()) {
case STATE_CHANGED:
parent.updateState();
break;
case RESULTS_ADDED:
listeners.broadcast(RemoteLibraryEvent.createResultsAddedEvent(parent, event.getAddedResults(),
// it's difficult to know the exact offset in the parent library, so let's not expose it
-1));
break;
case RESULTS_CLEARED:
if (parent.size() == 0) {
listeners.broadcast(RemoteLibraryEvent.createResultsClearedEvent(parent));
} else {
listeners.broadcast(RemoteLibraryEvent.createResultsRemovedEvent(parent));
}
break;
case RESULTS_REMOVED:
listeners.broadcast(RemoteLibraryEvent.createResultsRemovedEvent(parent));
break;
}
}
}
}