package org.limewire.core.impl.library; import java.awt.EventQueue; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.core.api.library.FriendLibrary; import org.limewire.core.api.library.LibraryState; import org.limewire.core.api.library.PresenceLibrary; import org.limewire.core.api.library.RemoteLibraryManager; import org.limewire.core.api.library.SearchResultList; import org.limewire.core.api.search.SearchResult; import org.limewire.friend.api.Friend; import org.limewire.friend.api.FriendPresence; import org.limewire.inspection.DataCategory; import org.limewire.inspection.Inspectable; import org.limewire.inspection.InspectableContainer; import org.limewire.inspection.InspectionPoint; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.util.StringUtils; import com.google.inject.Inject; import com.google.inject.Singleton; import ca.odell.glazedlists.BasicEventList; import ca.odell.glazedlists.CompositeList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.GlazedLists; import ca.odell.glazedlists.ObservableElementList; import ca.odell.glazedlists.ObservableElementList.Connector; import ca.odell.glazedlists.TransformedList; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventAssembler; import ca.odell.glazedlists.event.ListEventListener; import ca.odell.glazedlists.event.ListEventPublisher; import ca.odell.glazedlists.impl.ReadOnlyList; import ca.odell.glazedlists.util.concurrent.LockFactory; import ca.odell.glazedlists.util.concurrent.ReadWriteLock; /** * 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 AllFriendsLibraryImpl allFriendsList; private final EventList<FriendLibrary> allFriendLibraries; private final EventList<FriendLibrary> readOnlyFriendLibraries; private volatile EventList<FriendLibrary> swingFriendLibraries; private final ReadWriteLock lock; @SuppressWarnings("unused") @InspectableContainer private class LazyInspectableContainer { @InspectionPoint(value = "remote libraries", category = DataCategory.USAGE) private final Inspectable inspectable = new Inspectable() { @Override public Object inspect() { Map<String, Object> data = new HashMap<String, Object>(); readOnlyFriendLibraries.getReadWriteLock().readLock().lock(); try { List<Integer> sizes = new ArrayList<Integer>(readOnlyFriendLibraries.size()); for (FriendLibrary friendLibrary : readOnlyFriendLibraries) { sizes.add(friendLibrary.size()); } data.put("sizes", sizes); } finally { readOnlyFriendLibraries.getReadWriteLock().readLock().unlock(); } return data; } }; } @Inject public RemoteLibraryManagerImpl() { Connector<FriendLibrary> connector = GlazedLists.beanConnector(FriendLibrary.class); lock = LockFactory.DEFAULT.createReadWriteLock(); allFriendLibraries = GlazedListsFactory.observableElementList(GlazedListsFactory.threadSafeList(new BasicEventList<FriendLibrary>(lock)), connector); readOnlyFriendLibraries = GlazedListsFactory.readOnlyList(allFriendLibraries); allFriendsList = new AllFriendsLibraryImpl(lock); } @Override public SearchResultList getAllFriendsFileList() { return allFriendsList; } @Override public EventList<FriendLibrary> getFriendLibraryList() { return readOnlyFriendLibraries; } @Override public EventList<FriendLibrary> getSwingFriendLibraryList() { assert EventQueue.isDispatchThread(); if(swingFriendLibraries == null) { swingFriendLibraries = GlazedListsFactory.swingThreadProxyEventList(readOnlyFriendLibraries); } return swingFriendLibraries; } @Override public boolean addPresenceLibrary(FriendPresence presence) { assert !presence.getFriend().isAnonymous(); lock.writeLock().lock(); try { FriendLibraryImpl friendLibrary = getOrCreateFriendLibrary(presence.getFriend()); return friendLibrary.addPresenceLibrary(presence); } finally { lock.writeLock().unlock(); } } @Override public PresenceLibrary getPresenceLibrary(FriendPresence presence) { lock.readLock().lock(); try { FriendLibraryImpl friendLibrary = getFriendLibrary(presence.getFriend()); if(friendLibrary != null) { return friendLibrary.getPresenceLibrary(presence); } else { return null; } } finally { lock.readLock().unlock(); } } @Override public void removeFriendLibrary(Friend friend) { lock.writeLock().lock(); try { FriendLibraryImpl friendLibrary = getFriendLibraryImpl(friend); while(friendLibrary.allPresenceLibraries.size() > 0) { friendLibrary.removePresenceLibrary(friendLibrary.allPresenceLibraries.get(0).getPresence()); } removeFriendLibrary(friendLibrary); } finally { lock.writeLock().unlock(); } } @Override public void removePresenceLibrary(FriendPresence presence) { lock.writeLock().lock(); try { FriendLibraryImpl friendLibrary = getFriendLibraryImpl(presence.getFriend()); if (friendLibrary != null) { friendLibrary.removePresenceLibrary(presence); if (friendLibrary.getPresenceLibraryList().size() == 0) { removeFriendLibrary(friendLibrary); } } } finally { lock.writeLock().unlock(); } } private FriendLibraryImpl findFriendLibrary(Friend friend) { for(FriendLibrary library : allFriendLibraries) { if(library.getFriend().getId().equals(friend.getId())) { return (FriendLibraryImpl)library; } } return null; } private FriendLibraryImpl getOrCreateFriendLibrary(Friend friend) { FriendLibraryImpl friendLibrary = findFriendLibrary(friend); if(friendLibrary == null) { LOG.debugf("adding friend library for {0}", friend); friendLibrary = new FriendLibraryImpl(allFriendsList, friend, lock); allFriendsList.addMemberList(friendLibrary); friendLibrary.commit(); allFriendLibraries.add(friendLibrary); } return friendLibrary; } @Override public boolean hasFriendLibrary(Friend friend) { lock.readLock().lock(); try { return getFriendLibrary(friend) != null; } finally { lock.readLock().unlock(); } } @Override public FriendLibraryImpl getFriendLibrary(Friend friend) { return findFriendLibrary(friend); } private FriendLibraryImpl getFriendLibraryImpl(Friend friend) { return findFriendLibrary(friend); } private void removeFriendLibrary(FriendLibraryImpl friendLibrary) { LOG.debugf("removing friend library for {0}", friendLibrary.getFriend()); allFriendLibraries.remove(friendLibrary); allFriendsList.removeMemberList(friendLibrary); friendLibrary.dispose(); } private static class AllFriendsLibraryImpl implements SearchResultList { private final CompositeList<SearchResult> compositeList; private final ReadOnlyList<SearchResult> readOnlyList; private final EventList<SearchResult> threadSafeList; private volatile TransformedList<SearchResult, SearchResult> swingList; public AllFriendsLibraryImpl(ReadWriteLock lock) { compositeList = new CompositeList<SearchResult>(ListEventAssembler.createListEventPublisher(), lock); readOnlyList = GlazedListsFactory.readOnlyList(compositeList); threadSafeList = GlazedListsFactory.threadSafeList(readOnlyList); } @Override public EventList<SearchResult> getModel() { return threadSafeList; } @Override public EventList<SearchResult> getSwingModel() { assert EventQueue.isDispatchThread(); if(swingList == null) { swingList = GlazedListsFactory.swingThreadProxyEventList(threadSafeList); } return swingList; } @Override public int size() { return threadSafeList.size(); } ListEventPublisher getPublisher() { return compositeList.getPublisher(); } void removeMemberList(FriendLibrary friendLibrary) { compositeList.removeMemberList(friendLibrary.getModel()); } void addMemberList(FriendLibrary friendLibrary) { compositeList.addMemberList(friendLibrary.getModel()); } @Override public void addNewResult(SearchResult file) { throw new UnsupportedOperationException(); } @Override public void removeResult(SearchResult file) { throw new UnsupportedOperationException(); } @Override public void setNewResults(Collection<SearchResult> files) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } private static class FriendLibraryImpl implements FriendLibrary { private final Friend friend; private final ObservableElementList<PresenceLibrary> allPresenceLibraries; private final ReadOnlyList<PresenceLibrary> readOnlyPresenceLibraries; private final CompositeList<SearchResult> compositeList; private final ReadOnlyList<SearchResult> readOnlyList; private final EventList<SearchResult> threadSafeList; private volatile TransformedList<SearchResult, SearchResult> swingList; private final ReadWriteLock lock; private final PropertyChangeSupport changeSupport; private volatile LibraryState state = LibraryState.LOADING; public FriendLibraryImpl(AllFriendsLibraryImpl allFriendsList, Friend friend, ReadWriteLock lock) { this.friend = friend; this.lock = lock; compositeList = new CompositeList<SearchResult>(allFriendsList.getPublisher(), lock); readOnlyList = GlazedListsFactory.readOnlyList(compositeList); threadSafeList = GlazedListsFactory.threadSafeList(readOnlyList); changeSupport = new PropertyChangeSupport(this); Connector<PresenceLibrary> connector = GlazedLists.beanConnector(PresenceLibrary.class); allPresenceLibraries = GlazedListsFactory.observableElementList(GlazedListsFactory.threadSafeList(new BasicEventList<PresenceLibrary>(lock)), connector); readOnlyPresenceLibraries = GlazedListsFactory.readOnlyList(allPresenceLibraries); allPresenceLibraries.addListEventListener(new ListEventListener<PresenceLibrary>() { @Override public void listChanged(ListEvent<PresenceLibrary> listChanges) { LibraryState oldState = state; state = calculateState(); changeSupport.firePropertyChange("state", oldState, state); } }); } @Override public EventList<PresenceLibrary> getPresenceLibraryList() { return readOnlyPresenceLibraries; } @Override public LibraryState getState() { return state; } private LibraryState calculateState() { lock.readLock().lock(); try { boolean oneCompleted = false; for(PresenceLibrary library : allPresenceLibraries) { switch (library.getState()) { case LOADING: return LibraryState.LOADING; case LOADED: oneCompleted = true; break; } } if(oneCompleted) { return LibraryState.LOADED; } else { return LibraryState.FAILED_TO_LOAD; } } finally { lock.readLock().unlock(); } } public Friend getFriend() { return friend; } private PresenceLibraryImpl getPresenceLibrary(FriendPresence presence) { for(PresenceLibrary library : allPresenceLibraries) { if(library.getPresence().getPresenceId().equals(presence.getPresenceId())) { return (PresenceLibraryImpl)library; } } return null; } private boolean addPresenceLibrary(FriendPresence presence) { PresenceLibraryImpl library = getPresenceLibrary(presence); if(library == null) { LOG.debugf("adding presence library for {0}", presence); library = new PresenceLibraryImpl(presence, createMemberList()); allPresenceLibraries.add(library); addMemberList(library); library.commit(); return true; } else { return false; } } private void removePresenceLibrary(FriendPresence presence) { PresenceLibraryImpl presenceLibrary = getPresenceLibrary(presence); if(presenceLibrary != null) { LOG.debugf("removing presence library for {0}", presence); allPresenceLibraries.remove(presenceLibrary); presenceLibrary.dispose(); removeMemberList(presenceLibrary); } } private void removeMemberList(PresenceLibrary presenceLibrary) { compositeList.removeMemberList(presenceLibrary.getModel()); } private EventList<SearchResult> createMemberList() { return compositeList.createMemberList(); } private void addMemberList(PresenceLibrary presenceLibrary) { compositeList.addMemberList(presenceLibrary.getModel()); } @Override public EventList<SearchResult> getModel() { return threadSafeList; } @Override public EventList<SearchResult> getSwingModel() { assert EventQueue.isDispatchThread(); if(swingList == null) { swingList = GlazedListsFactory.swingThreadProxyEventList(threadSafeList); } return swingList; } @Override public int size() { return threadSafeList.size(); } @Override public void addNewResult(SearchResult file) { throw new UnsupportedOperationException(); } @Override public void removeResult(SearchResult file) { throw new UnsupportedOperationException(); } @Override public void setNewResults(Collection<SearchResult> file) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } void commit() { } void dispose() { if(swingList != null) { swingList.dispose(); } compositeList.dispose(); readOnlyList.dispose(); threadSafeList.dispose(); readOnlyPresenceLibraries.dispose(); allPresenceLibraries.dispose(); } public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } @Override public String toString() { return StringUtils.toString(this); } } private static class PresenceLibraryImpl implements PresenceLibrary { protected final EventList<SearchResult> eventList; protected volatile EventList<SearchResult> swingEventList; private final FriendPresence presence; private volatile LibraryState state = LibraryState.LOADING; private final PropertyChangeSupport changeSupport; PresenceLibraryImpl(FriendPresence presence, EventList<SearchResult> list) { this.presence = presence; eventList = GlazedListsFactory.threadSafeList(list); changeSupport = new PropertyChangeSupport(this); } @Override public String toString() { return StringUtils.toString(this, presence); } public FriendPresence getPresence() { return presence; } @Override public EventList<SearchResult> getModel() { return eventList; } @Override public EventList<SearchResult> getSwingModel() { assert EventQueue.isDispatchThread(); if(swingEventList == null) { swingEventList = GlazedListsFactory.swingThreadProxyEventList(eventList); } return swingEventList; } void dispose() { //eventList.clear(); if(swingEventList != null) { swingEventList.dispose(); } eventList.dispose(); } @Override public void addNewResult(SearchResult file) { eventList.add(file); } @Override public void removeResult(SearchResult file) { eventList.remove(file); } @Override public void setNewResults(Collection<SearchResult> files) { eventList.getReadWriteLock().writeLock().lock(); try { eventList.clear(); eventList.addAll(files); } finally { eventList.getReadWriteLock().writeLock().unlock(); } } @Override public void clear() { eventList.clear(); } @Override public int size() { return eventList.size(); } void commit() { // Add things here after we guarantee we want to use this list. } @Override public LibraryState getState() { return state; } @Override public void setState(LibraryState newState) { LibraryState oldState = state; this.state = newState; changeSupport.firePropertyChange("state", oldState, newState); } public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } } }