/**************************************************************************************************
* 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<Book> bookList;
private List<String> genreList = new ArrayList<>();
private List<String> 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<Book> 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<Book> 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<Book> 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 <tt>true</tt> 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 <tt>true</tt> 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<Book> emptyBookshelf() {
List<Book> 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 <tt>-1</tt> 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 <tt>-1</tt> 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 <tt>"author"</tt> and <tt>"genre"</tt>.
*
* @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"));
}
}
Comments