/************************************************************************************************** * Program Name: Programming Project 5.7: Bookshelf * Class Name: pp5_7.Bookshelf * Author: Terry Weiss * Date Written: October 15, 2015 * Program Description: * This class contains instance data for a bookshelf that is full of `Book`s. It allows the * User to store a book, browse the list of books, view the details of a particular book and * take a book off the shelf. **************************************************************************************************/ package pp5_7; //import pp5_7.Book; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Scanner; /** * Contains instance data for a bookshelf that is full of {@link pp5_7.Book}s. It allows the User to * store a book, browse the list of books, view the details of a particular book and take a book off * the Bookshelf. * * @author Terry Weiss * @see pp5_7.Book * @see pp5_7.Librarian */ public class Bookshelf { private final String DEFAULT_NAME = "Bookshelf"; /* * Holds all {@link pp5_7.Book}s, genres and authors. Genres and authors are saved here to be * quickly referenced when browsing by category. */ private List bookList; private List genreList = new ArrayList<>(); private List authorList = new ArrayList<>(); private final String name; /** * Constructs a new Bookshelf instance using the default name. */ public Bookshelf() { name = DEFAULT_NAME; bookList = new ArrayList<>(); } /** * Constructs a new Bookshelf instance using the name provided. * * @param name Name of the bookshelf */ public Bookshelf( String name ) { this.name = name; bookList = new ArrayList<>(); } /** * Constructs a new Bookshelf instance, using the default name, populated by a list of * {@link pp5_7.Book}s. Bookshelf's size will be originally allocated to 150% the size of the * loaded list. * * @param loadedList Bookshelf to be loaded */ public Bookshelf( List loadedList ) { name = DEFAULT_NAME; // Initialize with more space following default +10 rule bookList = new ArrayList<>(loadedList.size() + 10); // Add each book "manually" instead of copying everything, in case handling changes later. for ( Book book : loadedList ) { addBook(book); } } /** * Constructs a new Bookshelf instance, using the specified name, populated by a list of * {@link pp5_7.Book}s. Bookshelf's size will be originally allocated to 150% the size of the * loaded list. * * @param loadedList Bookshelf to be loaded * @param name Name of the bookshelf */ public Bookshelf( List loadedList, String name ) { this.name = name; bookList = new ArrayList<>(loadedList.size() + 10); for (Book book : loadedList) { addBook(book); } } /** * Gives current number of {@link pp5_7.Book}s on the shelf. * * @return Number of Books */ public int getNumberOfBooks() { return bookList.size(); } /** * Provides the current list of {@link pp5_7.Book}s on the shelf as a * {@link java.util.List}. * * @return All Book objects in a List */ public List getBookList() { return bookList; } /** * Provides a {@link pp5_7.Book} object from the shelf, retrieved by its location. * * @param location Location of the desired book * @return Book object retrieved */ public Book getBook( int location ) { return bookList.get(location); } /** * Provides a {@link pp5_7.Book} object from the shelf, retrieved by its title. * * @param title Title of the desired book * @return Book object retrieved */ public Book getBook( String title ) { return bookList.get(findBook(title)); } /** * Gives the name of the bookshelf. * * @return Bookshelf's name */ public String getName() { return name; } /** * Adds a pre-created {@link pp5_7.Book} to the shelf. * * @param book Book instance to be added * @return Book instance that was added */ public final Book addBook( Book book ) { bookList.add(book); String genre = book.getGenre(); if (!genreList.contains(genre)) { genreList.add(genre); } String author = book.getAuthor(); if (!authorList.contains(author)) { authorList.add(author); } return book; } /** * Removes a specific {@link pp5_7.Book} object from the Bookshelf. * * @param book Book instance to be removed * @return true if the Book was successfully removed */ public boolean removeBook( Book book ) { // If it's the only book of that genre or author, remove the genre/author from its list String genre = book.getGenre(); if (numBooksWithGenre(genre) == 1) { genreList.remove(genre); } String author = book.getAuthor(); if (numBooksWithAuthor(author) == 1) { authorList.remove(author); } return bookList.remove(book); } /** * Removes {@link pp5_7.Book} from the Bookshelf. Replaced by * {@link #removeBook(java.lang.String)}. Only use if the book being removed must be saved, * since the positions change with each sort. * * @param position Position of the Book in the list * @return Book instance being removed */ public Book removeBook( int position ) { Book book = bookList.get(position); String genre = book.getGenre(); if (numBooksWithGenre(genre) == 1) { genreList.remove(genre); } String author = book.getAuthor(); if (numBooksWithGenre(genre) == 1) { authorList.remove(author); } return bookList.remove(position); } /** * Removes {@link pp5_7.Book} from the Bookshelf. * * @param title Title of the Book to be removed * @return true if the Book was successfully removed */ public boolean removeBook( String title ) { if (numBooksWithTitle(title) == 1) { removeBook(findBook(title)); return true; } else { return false; } } /** * Removes all {@link pp5_7.Book}s from the Bookshelf. * * @return List of Books prior to deletion */ public List emptyBookshelf() { List list = bookList; for (int i = 0; i < bookList.size(); i++) { removeBook(i); } return list; } /** * Provides details of a specific {@link pp5_7.Book} from the Bookshelf, or if the Book doesn't * exist. Only use in cases where you want only one version of a Book that has a non-unique * title, since the positions change with each sort. * * @param position Position of Book to be viewed * @deprecated Replaced by {@link #viewBook(java.lang.String)} * @return String holding the details of the book if it exists */ public String viewBook( int position ) { String ret; if (position < 0 || position >= bookList.size()) { ret = "That book isn't on the shelf."; } else { ret = bookList.get(position).displayDetails(); } return ret; } /** * Provides details of a specific {@link pp5_7.Book} from the Bookshelf, or if the Book doesn't * exist. In cases where the title isn't unique, all Books by this title are shown and separated * by an empty line. * * @param title Title of the Book * @return String holding the details of the Books by that title */ public String viewBook( String title ) { String details = ""; for (Book currentBook : bookList) { if (currentBook.getTitle().equalsIgnoreCase(title)) { if (details.length() > 0) { // Separate each entry by a blank line in cases details += "\n\n"; // of duplicate titles } details += currentBook.displayDetails(); } } if (details.length() == 0) { details = title + " isn't on the shelf."; } return details; } /** * Sorts all {@link pp5_7.Book}s in ascending alphabetical order by title. * * @see pp5_7.Book#sortByTitle */ public void sortBooksByTitle() { Collections.sort(bookList, Book.sortByTitle); } /** * Sorts all {@link pp5_7.Book}s in ascending alphabetical by Author first and then by Title. * * @see pp5_7.Book#sortByAuthor */ public void sortBooksByAuthor() { // Sorts must be done in reverse order to get benefit of sorting by Title while not // overriding the sort by author. sortBooksByTitle(); Collections.sort(bookList, Book.sortByAuthor); } /** * Sorts all {@link pp5_7.Book}s in ascending alphabetical by Genre first and then by Title. * * @see pp5_7.Book#sortByGenre */ public void sortBooksByGenre() { sortBooksByTitle(); Collections.sort(bookList, Book.sortByGenre); } /** * Sorts all {@link pp5_7.Book}s in ascending numerical order by size first and then by Title * in cases where two books are the same size. */ public void sortBooksBySize() { sortBooksByTitle(); Collections.sort(bookList); } /** * Returns the index of the first occurrence of the {@link pp5_7.Book} in this list, or -1 if * this list does not contain the Book. * * @param book Book object to search for * @return Index of the first Book, or -1 if it's not there */ public int findBook( Book book ) { return bookList.indexOf(book); } /** * Returns the index of the first occurrence of the {@link pp5_7.Book} of the same title in this * list, or -1 if this list does not contain the Book. * * @param title Title of the book to search for * @return Index of the first Book, or -1 if it's not there */ public int findBook( String title ) { for (int currentBook = 0; currentBook < bookList.size(); currentBook++) { String currentTitle = bookList.get(currentBook).getTitle(); if (currentTitle.equalsIgnoreCase(title)) { return currentBook; } } return -1; } /** * Provides the number of {@link pp5_7.Book}s of a certain genre. * * @param genre Name of the genre * @return Number of Books of the provided genre */ public int numBooksWithGenre( String genre ) { int books = 0; for (Book book : bookList ) { if (book.getGenre().equalsIgnoreCase(genre)) { books++; } } return books; } /** * Provides the number of {@link pp5_7.Book}s of a certain author. * * @param author Name of the author * @return Number of Books by the provided author */ public int numBooksWithAuthor( String author ) { int books = 0; for (Book book : bookList ) { if (book.getAuthor().equalsIgnoreCase(author)) { books++; } } return books; } /** * Provides the number of {@link pp5_7.Book}s of a certain title. * * @param title Title to be searched for * @return Number of Books of the same title */ public int numBooksWithTitle( String title ) { int books = 0; for (Book book : bookList ) { if (book.getTitle().equalsIgnoreCase(title)) { books++; } } return books; } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf using their String * representation. * * @return List of all Books on the Bookshelf */ public String browse() { String list = ""; for (Book currentBook : bookList) { list += currentBook + "\n"; } return list; } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf using their String * representation. Books are resorted by title. * * @return List of all Books on the Bookshelf, sorted by title * @see #sortBooksByTitle */ public String browseByTitle() { sortBooksByTitle(); return browse(); } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf using their String * representation. Books are resorted by author's last name. * * @return List of all Books on the Bookshelf, sorted by author * @see #sortBooksByAuthor */ public String browseByAuthor() { sortBooksByAuthor(); String list = ""; for (Book currentBook : bookList ) { list += String.format("%-20s %s\n", currentBook, currentBook.getAuthor()); } return list; } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf using their String * representation. Books are resorted by genre. * * @return List of all Books on the Bookshelf, sorted by genre * @see #sortBooksByGenre */ public String browseByGenre() { sortBooksByGenre(); String list = ""; for (Book currentBook : bookList ) { list += String.format("%-20s %s\n", currentBook, currentBook.getGenre()); } return list; } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf using their String * representation. Books are resorted by size. * * @return List of all Books on the Bookshelf, sorted by size * @see #sortBooksBySize */ public String browseBySize() { sortBooksBySize(); String list = ""; for (Book currentBook : bookList ) { list += String.format("%-20s %s\n", currentBook, currentBook.getPages()); } return list; } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf in a specific genre, or if * no books in that genre exist. Books are sorted by title. * * @param genre Genre to be listed * @return List of Books in the given genre */ public String browseBooksOfGenre( String genre ) { String list = ""; // Make the genre Capitalized to match format of genres and be case-insensitive genre = genre.substring(0, 1).toUpperCase() + genre.substring(1).toLowerCase(); if (genreList.indexOf(genre) == -1) { list = "There are no books of that genre.\n"; } else { sortBooksByGenre(); int i = 0; // Find first book with the genre while (!bookList.get(i).getGenre().equalsIgnoreCase(genre)) { i++; } // Display all books until the genre changes or until it's the last book of the shelf while (i < bookList.size() && bookList.get(i).getGenre().equalsIgnoreCase(genre)) { list += bookList.get(i) + "\n"; i++; } } return list; } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf by a specific author, or if * no books by that author exist. Books are sorted by title. * * @param author Author to be listed * @return List of Books by the given author */ public String browseBooksOfAuthor( String author ) { String list = ""; // Make the genre Capitalized to match format of genres and be case-insensitive String capitalizedAuthor = ""; for (String authorName : author.split(" ")) { capitalizedAuthor += authorName.substring(0, 1).toUpperCase() + authorName.substring(1).toLowerCase() + " "; } capitalizedAuthor = capitalizedAuthor.trim(); if (authorList.indexOf(capitalizedAuthor) == -1) { list = "There are no books by that author.\n"; } else { sortBooksByAuthor(); int i = 0; // Find first book with the genre while (!bookList.get(i).getAuthor().equalsIgnoreCase(capitalizedAuthor)) { i++; } // Display all books until the genre changes or until it's the last book of the shelf while (i < bookList.size() && bookList.get(i).getAuthor() .equalsIgnoreCase(capitalizedAuthor)) { list += bookList.get(i) + "\n"; i++; } } return list; } /** * Provides a String of all {@link pp5_7.Book}s on the Bookshelf in a specific category, or if * no books in that category exist. Books are sorted by title. Currently, the only supported * categories are "author" and "genre". * * @param category Category to be used * @return List of Books in the given category */ public String browseByCategory( String category ) { String list = ""; if (category.equalsIgnoreCase("author") || category.equalsIgnoreCase("authors")) { for (String author : authorList) { list += author + ":\n" + browseBooksOfAuthor(author) + "\n"; } } else if (category.equalsIgnoreCase("genre") || category.equalsIgnoreCase("genres")) { for (String genre : genreList) { list += genre + ":\n" + browseBooksOfGenre(genre) + "\n"; } } else { list = "No such category.\n"; } return list; } /** * String representation of the Bookshelf * * @return Bookshelf's name */ @Override public String toString() { return getName(); } /** * Used only for debugging purposes * * @param args Command line input isn't supported */ public static void main( String[] args ) { Scanner user_input = new Scanner(System.in); Bookshelf myShelf = new Bookshelf(); myShelf.addBook(new Book("book1", 321, "martin", "fiction")); myShelf.addBook(new Book("book2", 123, "Martin", "non-fiction")); myShelf.addBook(new Book("book3", 852, "Peter", "sci-fi")); myShelf.addBook(new Book("book4", 753, "Martin", "fiction")); myShelf.addBook(new Book("book5", 159, "Ralph", "non-fiction")); myShelf.addBook(new Book("book6", 349, "MartIn", "fiction")); myShelf.removeBook("book2"); System.out.print(myShelf.browseByCategory("author")); System.out.println(myShelf.viewBook("book1")); } }