package org.limewire.core.impl.library; import java.awt.EventQueue; import java.io.File; import java.io.FileFilter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import org.limewire.collection.glazedlists.GlazedListsFactory; import org.limewire.concurrent.ListeningFuture; import org.limewire.concurrent.ListeningFutureDelegator; import org.limewire.core.api.URN; import org.limewire.core.api.library.LocalFileItem; import org.limewire.core.api.library.LocalFileList; import org.limewire.listener.EventListener; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.TransformedList; import com.google.common.base.Predicate; import com.limegroup.gnutella.library.FileCollection; import com.limegroup.gnutella.library.FileDesc; import com.limegroup.gnutella.library.FileViewChangeEvent; abstract class LocalFileListImpl implements LocalFileList { private static final Log LOG = LogFactory.getLog(LocalFileListImpl.class); protected final EventList<LocalFileItem> baseList; protected final TransformedList<LocalFileItem, LocalFileItem> threadSafeList; protected final TransformedList<LocalFileItem, LocalFileItem> readOnlyList; protected volatile TransformedList<LocalFileItem, LocalFileItem> swingEventList; private final CoreLocalFileItemFactory fileItemFactory; LocalFileListImpl(EventList<LocalFileItem> eventList, CoreLocalFileItemFactory fileItemFactory) { this.baseList = eventList; this.threadSafeList = GlazedListsFactory.threadSafeList(eventList); this.readOnlyList = GlazedListsFactory.readOnlyList(threadSafeList); this.fileItemFactory = fileItemFactory; } /** Returns the FileCollection this should mutate. */ protected abstract FileCollection getCoreCollection(); @Override public ListeningFuture<LocalFileItem> addFile(File file) { return new Wrapper((getCoreCollection().add(file))); } @Override public void removeFile(File file) { getCoreCollection().remove(file); } @Override public ListeningFuture<List<ListeningFuture<LocalFileItem>>> addFolder(File folder, FileFilter fileFilter) { return new ListWrapper((getCoreCollection().addFolder(folder, fileFilter))); } @Override public boolean contains(File file) { return getCoreCollection().contains(file); } @Override public boolean contains(URN urn) { if(urn instanceof com.limegroup.gnutella.URN) { return containsCoreUrn((com.limegroup.gnutella.URN)urn); } else { return false; } } protected boolean containsCoreUrn(com.limegroup.gnutella.URN urn) { return !getCoreCollection().getFileDescsMatching(urn).isEmpty(); } @Override public EventList<LocalFileItem> getModel() { return readOnlyList; } @Override public EventList<LocalFileItem> getSwingModel() { assert EventQueue.isDispatchThread(); if(swingEventList == null) { swingEventList = GlazedListsFactory.swingThreadProxyEventList(readOnlyList); } return swingEventList; } void dispose() { if(swingEventList != null) { swingEventList.dispose(); } threadSafeList.dispose(); readOnlyList.dispose(); } @Override public int size() { return threadSafeList.size(); } /** * Adds <code>fd</code> as {@link LocalFileItem} to this list. */ protected void addFileDesc(FileDesc fd) { threadSafeList.add(getOrCreateLocalFileItem(fd)); } private LocalFileItem getOrCreateLocalFileItem(FileDesc fileDesc) { LocalFileItem item; Object object = fileDesc.getClientProperty(FILE_ITEM_PROPERTY); if(object != null) { item = (LocalFileItem)object; } else { item = fileItemFactory.createCoreLocalFileItem(fileDesc); fileDesc.putClientProperty(FILE_ITEM_PROPERTY, item); } return item; } /** * Adds all <code>fileDescs</code> as {@link LocalFileItem} to this list. * <p> * Caller is responsible for locking the iterable. */ protected void addAllFileDescs(Iterable<FileDesc> fileDescs) { List<LocalFileItem> fileItems = new ArrayList<LocalFileItem>(); for (FileDesc fileDesc : fileDescs) { fileItems.add(getOrCreateLocalFileItem(fileDesc)); } threadSafeList.addAll(fileItems); } /** Notification that meta information has changed in the filedesc. */ protected void updateFileDesc(FileDesc fd) { LocalFileItem item = (LocalFileItem)fd.getClientProperty(FILE_ITEM_PROPERTY); if(item != null) { threadSafeList.getReadWriteLock().writeLock().lock(); try { int idx = threadSafeList.indexOf(item); if(idx > 0) { threadSafeList.set(idx, item); } else { LOG.warnf("Attempted to update FD w/ LocalFileItem that is not in list anymore. Item {0}", item); } } finally { threadSafeList.getReadWriteLock().writeLock().unlock(); } } else { LOG.warnf("Attempted to update FD without LocalFileItem, FD {0}", fd); } } protected void changeFileDesc(FileDesc old, FileDesc now) { removeFileDesc(old); addFileDesc(now); } protected void removeFileDesc(FileDesc fd) { LocalFileItem item = (LocalFileItem)fd.getClientProperty(FILE_ITEM_PROPERTY); threadSafeList.remove(item); } protected void clearFileDescs() { threadSafeList.clear(); } /** Constructs a new EventListener for list change events. */ protected EventListener<FileViewChangeEvent> newEventListener() { return new EventListener<FileViewChangeEvent>() { @Override public void handleEvent(FileViewChangeEvent event) { switch(event.getType()) { case FILE_META_CHANGED: updateFileDesc(event.getFileDesc()); break; case FILE_ADDED: addFileDesc(event.getFileDesc()); break; case FILE_CHANGED: changeFileDesc(event.getOldValue(), event.getFileDesc()); break; case FILE_REMOVED: removeFileDesc(event.getFileDesc()); break; case FILES_CLEARED: clearFileDescs(); break; } } }; } private static class ListWrapper extends ListeningFutureDelegator<List<ListeningFuture<FileDesc>>, List<ListeningFuture<LocalFileItem>>> { public ListWrapper(ListeningFuture<List<ListeningFuture<FileDesc>>> delegate) { super(delegate); } @Override protected List<ListeningFuture<LocalFileItem>> convertSource(List<ListeningFuture<FileDesc>> source) { List<ListeningFuture<LocalFileItem>> replaced = new ArrayList<ListeningFuture<LocalFileItem>>(source.size()); for(ListeningFuture<FileDesc> future : source) { replaced.add(new Wrapper(future)); } return replaced; } @Override protected List<ListeningFuture<LocalFileItem>> convertException(ExecutionException ee) throws ExecutionException { throw ee; } } private static class Wrapper extends ListeningFutureDelegator<FileDesc, LocalFileItem> { public Wrapper(ListeningFuture<FileDesc> delegate) { super(delegate); } @Override protected LocalFileItem convertSource(FileDesc source) { return (LocalFileItem)source.getClientProperty(FILE_ITEM_PROPERTY); } @Override protected LocalFileItem convertException(ExecutionException ee) throws ExecutionException { throw ee; } } @Override public LocalFileItem getFileItem(File file) { FileDesc fileDesc = getCoreCollection().getFileDesc(file); if(fileDesc != null) { return (LocalFileItem)fileDesc.getClientProperty(FILE_ITEM_PROPERTY); } return null; } @Override public LocalFileItem getFileItem(URN urn) { if (urn instanceof com.limegroup.gnutella.URN) { FileDesc fd = getCoreCollection().getFileDesc((com.limegroup.gnutella.URN)urn); if (fd != null) { return (LocalFileItem)fd.getClientProperty(FILE_ITEM_PROPERTY); } } return null; } @Override public boolean isFileAllowed(File file) { return getCoreCollection().isFileAllowed(file); } @Override public boolean isDirectoryAllowed(File folder) { return getCoreCollection().isDirectoryAllowed(folder); } @Override public void removeFiles(Predicate<LocalFileItem> filter) { List<LocalFileItem> files = new ArrayList<LocalFileItem>(); getModel().getReadWriteLock().readLock().lock(); try { for (LocalFileItem localFileItem : getModel()) { if (filter.apply(localFileItem)) { files.add(localFileItem); } } } finally { getModel().getReadWriteLock().readLock().unlock(); } for (LocalFileItem localFileItem : files) { removeFile(localFileItem.getFile()); } } @Override public void clear() { getCoreCollection().clear(); } }