package org.limewire.core.impl.playlist; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.limewire.core.api.library.LocalFileItem; import org.limewire.core.api.playlist.Playlist; import org.limewire.core.api.playlist.PlaylistItem; import org.limewire.core.api.playlist.PlaylistListener; import org.limewire.util.MediaType; import ca.odell.glazedlists.matchers.AbstractMatcherEditor; import ca.odell.glazedlists.matchers.Matcher; import ca.odell.glazedlists.matchers.MatcherEditor; /** * Live implementation of Playlist. */ class PlaylistImpl implements Playlist { private final MediaType mediaType; private final List<PlaylistItem> itemList; private final PlaylistMatcherEditor matcherEditor; private final List<PlaylistListener> playlistListeners; private String name; /** * Constructs a Playlist with the specified name. */ public PlaylistImpl(String name) { this.mediaType = MediaType.getAudioMediaType(); this.itemList = Collections.synchronizedList(new ArrayList<PlaylistItem>()); this.matcherEditor = new PlaylistMatcherEditor(); this.playlistListeners = new CopyOnWriteArrayList<PlaylistListener>(); this.name = name; } @Override public void addPlaylistListener(PlaylistListener listener) { playlistListeners.add(listener); } @Override public void removePlaylistListener(PlaylistListener listener) { playlistListeners.remove(listener); } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } /** * Adds the specified file to the end of the playlist. * At present, the same file cannot be added multiple times. */ @Override public void addFile(File file) { if (!contains(file)) { // Add playlist item to playlist. itemList.add(new PlaylistItemImpl(file)); // Update filter on local file items, and notify listeners. matcherEditor.update(); fireListChanged(); } } /** * Adds the specified file at the specified index in the playlist. * At present, the same file cannot be added multiple times. */ @Override public void addFile(int index, File file) { // Find file in current playlist. If it already exists, remove the // old entry and insert a new one at the specified index. int oldIndex = getIndex(file); if (oldIndex < 0) { itemList.add(index, new PlaylistItemImpl(file)); } else { PlaylistItem item = itemList.remove(oldIndex); itemList.add(index, item); } // Update filter on local file items, and notify listeners. matcherEditor.update(); fireListChanged(); } @Override public void removeFile(File file) { int oldIndex = getIndex(file); if (oldIndex >= 0) { // Remove playlist item from playlist. itemList.remove(oldIndex); // Update filter on local file items, and notify listeners. matcherEditor.update(); fireListChanged(); } } @Override public void reorderFiles(File[] files) { // This is a slow implementation because it physically moves the list // elements. A possible performance upgrade could use a row mapping. for (int i = 0; i < files.length; i++) { int oldIndex = getIndex(files[i]); if (oldIndex >= 0) { PlaylistItem item = itemList.remove(oldIndex); itemList.add(i, item); } } } @Override public void clear() { // Remove all items from playlist. itemList.clear(); // Update filter on local file items, and notify listeners. matcherEditor.update(); fireListChanged(); } @Override public boolean canAdd(File file) { return mediaType.matches(file.getName()); } @Override public boolean contains(File file) { return itemList.contains(new PlaylistItemImpl(file)); } @Override public int getIndex(File file) { return itemList.indexOf(new PlaylistItemImpl(file)); } @Override public MatcherEditor<LocalFileItem> getFilter() { return matcherEditor; } /** * Notifies all registered listeners that the playlist has changed. */ private void fireListChanged() { for (PlaylistListener listener : playlistListeners) { listener.listChanged(this); } } /** * Defines a MatcherEditor that notifies a Glazed List when the items in * the playlist have changed. */ private class PlaylistMatcherEditor extends AbstractMatcherEditor<LocalFileItem> { private final Matcher<LocalFileItem> matcher = new PlaylistMatcher(); @Override public Matcher<LocalFileItem> getMatcher() { return matcher; } /** * Updates the playlist by firing an event to notify listeners that the * matcher has changed. */ public void update() { fireChanged(matcher); } } /** * Defines a Matcher that can be applied to a Glazed List to display the * items in the playlist. */ private class PlaylistMatcher implements Matcher<LocalFileItem> { @Override public boolean matches(LocalFileItem item) { return contains(item.getFile()); } } }