Java tutorial
/** * This work is licensed under the Creative Commons Attribution-NonCommercial- * NoDerivs 3.0 Unported License. To view a copy of this license, visit * http://creativecommons.org/licenses/by-nc-nd/3.0/ or send a letter to * Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, * 94041, USA. * * Use of this work is permitted only in accordance with license rights granted. * Materials provided "AS IS"; no representations or warranties provided. * * Copyright 2012 Marcus Parkkinen, Aki Kkel, Fredrik hs. **/ package edu.chalmers.dat255.audiobookplayer.model; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.Serializable; import java.util.LinkedList; import java.util.List; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import android.util.Log; import edu.chalmers.dat255.audiobookplayer.constants.Constants; import edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates; /** * The bookshelf class contains a collection of books. * * @author Marcus Parkkinen, Aki Kkel * @version 0.6 * */ public class Bookshelf implements IBookUpdates, Serializable { private static final String TAG = "Bookshelf"; private static final int NO_BOOK_SELECTED = Constants.Value.NO_BOOK_SELECTED; private static final long serialVersionUID = 1L; private List<Book> books; private int selectedBookIndex; private transient PropertyChangeSupport pcs; /** * Creates an empty bookshelf. */ public Bookshelf() { books = new LinkedList<Book>(); selectedBookIndex = NO_BOOK_SELECTED; pcs = new PropertyChangeSupport(this); } /** * Copy constructor. * * @param original */ public Bookshelf(Bookshelf original) { this(); // copy instance variables this.selectedBookIndex = original.selectedBookIndex; // make deep copies of Book in the list for (Book b : original.books) { this.books.add(new Book(b)); } } /* Bookshelf methods */ /** * The book that the player will use (read from) is set here. Can be * deselecting by calling with Constants.Value.NO_BOOK_SELECTED. * * @param index * The index to select. -1 <= x <= last list index. */ public void setSelectedBookIndex(int index) { if (isValidBookIndex(index)) { selectedBookIndex = index; if (hasListeners()) { pcs.firePropertyChange(Constants.Event.BOOK_SELECTED, null, new Bookshelf(this)); } } } /** * Add a new book to the bookshelf. * * @param Book * the new book to add */ public void addBook(Book b) { books.add(b); // select it if it is the first, otherwise move the selection ahead so // that it is pointing at the correct book if (selectedBookIndex == NO_BOOK_SELECTED) { setSelectedBookIndex(0); // note: in future versions, the newly added book should always be // selected. } if (hasListeners()) { pcs.firePropertyChange(Constants.Event.BOOK_LIST_CHANGED, null, new Bookshelf(this)); } } /** * Remove a book from the bookshelf at the given index. * * @param int index */ public void removeBookAt(int index) { checkBookIndexLegal(index); books.remove(index); // check whether this was the last book if (books.size() == 0) { // deselect setSelectedBookIndex(NO_BOOK_SELECTED); } else { if (index < selectedBookIndex) { // adjust the index if we removed one earlier in the list setSelectedBookIndex(selectedBookIndex - 1); } else if (index == selectedBookIndex) { // if we removed the selected one then select the first setSelectedBookIndex(0); } } if (hasListeners()) { pcs.firePropertyChange(Constants.Event.BOOK_LIST_CHANGED, null, new Bookshelf(this)); } } /** * Move a book from a given index to a given index. Indices inbetween will * be adjusted. * * @param fromIndex * @param toIndex */ public void moveBook(int fromIndex, int toIndex) { checkBookIndexLegal(fromIndex); checkBookIndexLegal(toIndex); Book b = books.remove(fromIndex); books.add(toIndex, b); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.BOOK_LIST_CHANGED, null, new Bookshelf(this)); } } /** * Sets both the track index and the book index. * <p> * Both integers must be valid, and only the track index may be deselected * (set to "-1"). * * @param bookIndex * The index of the book to select. Can not be -1. * @param trackIndex * The index of the track to select. Can be -1. */ public void setSelectedTrackIndex(int bookIndex, int trackIndex) { // make no changes if book index is illegal or track index is invalid if (isLegalBookIndex(bookIndex) && isValidTrackIndex(bookIndex, trackIndex)) { // the book index must be legal // the track index can be unselected or legal // the track index is either valid or deselects // we now know that both indices are valid, so make the changes. setSelectedTrackIndex(trackIndex); } } /* End Bookshelf methods */ /* IBookUpdates */ /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates#removeTrack * (int) */ public void removeTrack(int index) { checkBookIndexLegal(selectedBookIndex); this.books.get(selectedBookIndex).removeTrack(index); /* * since we removed a track we need to recalculate the duration of the * book */ updateSelectedBookDuration(); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.TRACK_LIST_CHANGED, null, new Bookshelf(this)); } } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates#addTrack( * edu.chalmers.dat255.audiobookplayer.model.Track) */ public void addTrack(Track t) { checkBookIndexLegal(selectedBookIndex); this.books.get(selectedBookIndex).addTrack(t); /* * since we removed a track we need to recalculate the duration of the * book */ updateSelectedBookDuration(); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.TRACK_LIST_CHANGED, null, new Bookshelf(this)); } } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates#swapTracks * (int, int) */ public void swapTracks(int firstIndex, int secondIndex) { checkBookIndexLegal(selectedBookIndex); this.books.get(selectedBookIndex).swapTracks(firstIndex, secondIndex); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.TRACK_LIST_CHANGED, null, new Bookshelf(this)); } } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates#moveTrack * (int, int) */ public void moveTrack(int from, int to) { checkBookIndexLegal(selectedBookIndex); this.books.get(selectedBookIndex).moveTrack(from, to); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.TRACK_LIST_CHANGED, null, new Bookshelf(this)); } } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates# * setSelectedTrackIndex(int) */ public void setSelectedTrackIndex(int index) { checkBookIndexLegal(selectedBookIndex); this.books.get(selectedBookIndex).setSelectedTrackIndex(index); pcs.firePropertyChange(Constants.Event.TRACK_INDEX_CHANGED, null, new Bookshelf(this)); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates# * setSelectedBookTitle(java.lang.String) */ public void setSelectedBookTitle(String newTitle) { setBookTitleAt(selectedBookIndex, newTitle); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates# * getSelectedBookTitle() */ public String getSelectedBookTitle() { return getBookTitleAt(this.selectedBookIndex); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates# * getSelectedBookAuthor() */ public String getSelectedBookAuthor() { return this.books.get(selectedBookIndex).getSelectedBookAuthor(); } /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookUpdates# * updateBookDuration() */ public void updateSelectedBookDuration() { this.books.get(selectedBookIndex).updateSelectedBookDuration(); } /* End IBookUpdates */ /** * Set the title of the book at the given index. * * @param bookIndex * @param newTitle */ public void setBookTitleAt(int bookIndex, String newTitle) { checkBookIndexLegal(bookIndex); this.books.get(bookIndex).setSelectedBookTitle(newTitle); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.BOOK_TITLE_CHANGED, null, new Bookshelf(this)); } } /** * Gets the title of a book at a given index. * * @param bookIndex * Index to get the book title from. * @return Book title at a given index. */ public String getBookTitleAt(int bookIndex) { checkBookIndexLegal(bookIndex); return this.books.get(bookIndex).getSelectedBookTitle(); } /* ITrackUpdates */ /* * (non-Javadoc) * * @see edu.chalmers.dat255.audiobookplayer.interfaces.ITrackUpdates# * setSelectedTrackElapsedTime(int) */ public void setSelectedTrackElapsedTime(int elapsedTime) { if (selectedBookIndex == NO_BOOK_SELECTED) { throw new IndexOutOfBoundsException(TAG + " setSelectedTrackElapsedTime " + NO_BOOK_SELECTED); } // set elapsed time in the currently playing book books.get(selectedBookIndex).setSelectedTrackElapsedTime(elapsedTime); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.ELAPSED_TIME_CHANGED, null, new Bookshelf(this)); } } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.ITrackUpdates#addTag(int) */ public void addTag(int time) { checkBookIndexLegal(selectedBookIndex); this.books.get(selectedBookIndex).addTag(time); pcs.firePropertyChange(Constants.Event.TAG_ADDED, null, new Bookshelf(this)); } /* * (non-Javadoc) * * @see * edu.chalmers.dat255.audiobookplayer.interfaces.ITrackUpdates#removeTagAt * (int) */ public void removeTagAt(int tagIndex) { checkBookIndexLegal(selectedBookIndex); this.books.get(selectedBookIndex).removeTagAt(tagIndex); pcs.firePropertyChange(Constants.Event.TAG_REMOVED, null, new Bookshelf(this)); } /* End ITrackUpdates */ /* * Accessors to Bookshelf. */ /** * The number of books in the bookshelf. * * @return */ public int getNumberOfBooks() { return this.books.size(); } /** * The selected book index in the bookshelf. * * @return */ public int getSelectedBookIndex() { return this.selectedBookIndex; } /** * Selected book. * * @return */ public Book getSelectedBook() { checkBookIndexLegal(selectedBookIndex); return this.books.get(selectedBookIndex); } /** * Book at given index. * * @param bookIndex * @return */ public Book getBookAt(int bookIndex) { checkBookIndexLegal(bookIndex); return this.books.get(bookIndex); } /* * Accessors to Book. */ /** * @return The duration of the selected book. */ public int getSelectedBookDuration() { return getBookDurationAt(selectedBookIndex); } /** * @return Track index in the selected book. */ public int getSelectedTrackIndex() { return getTrackIndexAt(selectedBookIndex); } /** * @param bookIndex * Book to get the selected track in. * @return Track index in the given book. */ public int getTrackIndexAt(int bookIndex) { checkBookIndexLegal(bookIndex); return this.books.get(bookIndex).getSelectedTrackIndex(); } /** * ** currently unused ** * * @return */ public int getBookElapsedTime() { return getBookElapsedTimeAt(selectedBookIndex); } /** * @param bookIndex * @return */ public int getBookElapsedTimeAt(int bookIndex) { checkBookIndexLegal(bookIndex); return books.get(bookIndex).getBookElapsedTime(); } /** * The number of tracks in the selected book. * * @return */ public int getNumberOfTracks() { return getNumberOfTracksAt(selectedBookIndex); } /** * Gets the number of tracks the book at given position has * * @param bookIndex * Position of the book * @return Number of tracks of given book */ public int getNumberOfTracksAt(int bookIndex) { checkBookIndexLegal(bookIndex); return this.books.get(bookIndex).getNumberOfTracks(); } /* * Accessors to Track. */ /** * Selected book, track. * * @return */ public int getSelectedTrackDuration() { return getSelectedTrackDurationAt(selectedBookIndex); } /** * Gets duration of the selected track in a given book. * * @param bookIndex * Position of the book * @return */ public int getSelectedTrackDurationAt(int bookIndex) { checkBookIndexLegal(bookIndex); int trackIndex = books.get(bookIndex).getSelectedTrackIndex(); return getTrackDurationAt(bookIndex, trackIndex); } /** * Gets the duration of a given track in a given book. * * @param bookIndex * Position of the book * @param trackIndex * Position of the track * @return */ public int getTrackDurationAt(int bookIndex, int trackIndex) { checkTrackIndexLegalAt(bookIndex, trackIndex); return this.books.get(bookIndex).getTrackDurationAt(trackIndex); } /** * Track path of selected book and track. * * @return */ public String getSelectedTrackPath() { return getSelectedTrackPathAt(selectedBookIndex); } /** * Track path of given book, selected track. * * @param bookIndex * @return */ public String getSelectedTrackPathAt(int bookIndex) { checkBookIndexLegal(bookIndex); return this.books.get(bookIndex).getSelectedTrackPath(); } /** * Track path of given book and track. * * @param bookIndex * @param trackIndex * @return */ public String getTrackPathAt(int bookIndex, int trackIndex) { checkTrackIndexLegalAt(bookIndex, trackIndex); return this.books.get(bookIndex).getTrackPathAt(trackIndex); } /** * Checks whether a given index is within the legal bounds of the list of * books. * * @param index * Index to check. * @return True if within bounds. */ public boolean isLegalBookIndex(int index) { return index >= 0 && index < books.size(); } /** * Checks whether a given index is within the valid bounds of the list of * books. Includes the 'deselected' index. * * @param index * Index to check. * @return True if within bounds. */ private boolean isValidBookIndex(int index) { return isLegalBookIndex(index) || index == Constants.Value.NO_BOOK_SELECTED; } /** * Checks whether a given index is within the valid bounds of the list of * tracks. Includes the 'deselected' index. * * @param bookIndex * The book at which to check for validity. * @param trackIndex * Index to check. * @return True if within bounds. */ private boolean isValidTrackIndex(int bookIndex, int trackIndex) { return isLegalTrackIndexAt(bookIndex, trackIndex) || trackIndex == Constants.Value.NO_TRACK_SELECTED; } /** * Provides a check to see whether this model has listeners. If it does not, * updates are pointless. * * @return True if this object has elements in its property change listener * object. */ private boolean hasListeners() { return pcs.getPropertyChangeListeners().length > 0; } /** * Checks if the given track index is legal for the currently selected book. * * @param trackIndex * Track index to check if legal. * @return */ public boolean isLegalTrackIndex(int trackIndex) { return isLegalTrackIndexAt(selectedBookIndex, trackIndex); } /** * Checks if the given track index is legal for the given book. * * @param bookIndex * Book to check in. * @param trackIndex * Track index to check if legal. * @return */ private boolean isLegalTrackIndexAt(int bookIndex, int trackIndex) { checkBookIndexLegal(bookIndex); return books.get(bookIndex).isLegalTrackIndex(trackIndex); } /** * Adds a listener to this object's property change listener object. * * @param listener * Listener to add. */ public void addPropertyChangeListener(PropertyChangeListener listener) { if (listener != null) { pcs.addPropertyChangeListener(listener); /* * Synchronize the new listener with the current state of the * bookshelf. */ pcs.firePropertyChange(Constants.Event.BOOKSHELF_UPDATED, null, new Bookshelf(this)); } else { Log.e(TAG, " trying to add null as property change listener. Skipping operation."); } } /** * Removes all listeners from the pcs member. */ public void removeListeners() { for (PropertyChangeListener pcl : pcs.getPropertyChangeListeners()) { pcs.removePropertyChangeListener(pcl); } } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return new HashCodeBuilder().append(books).append(selectedBookIndex).toHashCode(); } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (obj instanceof Bookshelf) { final Bookshelf other = (Bookshelf) obj; return new EqualsBuilder().append(books, other.books).append(selectedBookIndex, other.selectedBookIndex) .isEquals(); } else { return false; } } /** * Gets the track title at given position * * @param bookIndex * Position of the book * @param trackIndex * Position of the track * @return Track title of given track */ public String getTrackTitleAt(int bookIndex, int trackIndex) { checkTrackIndexLegalAt(bookIndex, trackIndex); return this.books.get(bookIndex).getTrackTitleAt(trackIndex); } /** * BAD IMPLEMENTATION - CAUSES CRASH. * * Gets the duration of the given book * * @param bookIndex * Position of the book * @return */ public int getBookDurationAt(int bookIndex) { checkBookIndexLegal(bookIndex); return books.get(bookIndex).getDuration(); } /** * (has no selected method) Gets the author at given position * * @param bookIndex * Position of the book * @return The given book's author */ public String getBookAuthorAt(int bookIndex) { checkBookIndexLegal(bookIndex); return this.books.get(bookIndex).getSelectedBookAuthor(); } /** * If two or more tracks exist, remove the given track, otherwise remove the * book * * @param bookIndex * Index of the book * @param trackIndex * Index of the track */ public void removeTrack(int bookIndex, int trackIndex) { checkBookIndexLegal(bookIndex); checkTrackIndexLegalAt(bookIndex, trackIndex); // the event to fire String event; if (this.books.get(bookIndex).getNumberOfTracks() <= 1) { // remove the entire book (since there was just 1 track) removeBookAt(bookIndex); event = Constants.Event.BOOK_LIST_CHANGED; } else { // if more than 1 track, remove it this.books.get(bookIndex).removeTrack(trackIndex); // re-calculate the book duration updateBookDurationAt(bookIndex); event = Constants.Event.TRACK_LIST_CHANGED; } if (hasListeners()) { pcs.firePropertyChange(event, null, new Bookshelf(this)); } } /** * Private method to update the duration of the book at given index. * bookIndex shoulve have already been checked to be legal. * * @param bookIndex * Index of the book */ private void updateBookDurationAt(int bookIndex) { checkBookIndexLegal(bookIndex); this.books.get(bookIndex).updateSelectedBookDuration(); } /** * Moves a track given amount of steps. * * @param bookIndex * Index of the book * @param trackIndex * Index of the track * @param offset * The amount of steps to move the track, negative value moves * upward visually */ public void moveTrack(int bookIndex, int trackIndex, int offset) { checkBookIndexLegal(bookIndex); checkTrackIndexLegalAt(bookIndex, trackIndex); checkTrackIndexLegalAt(bookIndex, trackIndex + offset); this.books.get(bookIndex).moveTrack(trackIndex, trackIndex + offset); if (hasListeners()) { pcs.firePropertyChange(Constants.Event.BOOK_LIST_CHANGED, null, new Bookshelf(this)); } } /** * Convenience method. Checks if the index is valid for a book. * <p> * USED FOR DEBUGGING. * * @param index */ private void checkBookIndexLegal(int index) { if (!isLegalBookIndex(index)) { throw new IndexOutOfBoundsException("Book index is illegal: " + index); } } /** * Convenience method. Checks if the index is valid for a track in a book. * <p> * USED FOR DEBUGGING. * * @param bookIndex * Book to check in. * @param trackIndex * Track to check. */ private void checkTrackIndexLegalAt(int bookIndex, int trackIndex) { if (!isLegalTrackIndexAt(bookIndex, trackIndex)) { throw new IndexOutOfBoundsException( "Track or book index is illegal (book, track): " + bookIndex + ", " + trackIndex); } } }