001package edu.pdx.cs410J.di; 002 003import com.google.inject.Inject; 004import com.google.inject.Singleton; 005import jakarta.xml.bind.JAXBException; 006import jakarta.xml.bind.annotation.XmlAttribute; 007import jakarta.xml.bind.annotation.XmlElement; 008import jakarta.xml.bind.annotation.XmlElementWrapper; 009import jakarta.xml.bind.annotation.XmlRootElement; 010 011import java.io.File; 012import java.io.IOException; 013import java.util.*; 014import java.util.concurrent.atomic.AtomicInteger; 015 016/** 017 * An implementation of {@link BookInventory} that stores a database of books in a flat file 018 */ 019@Singleton 020public class BookDatabase extends JaxbDatabase implements BookInventory 021{ 022 023 private final Map<Book, AtomicInteger> inventory; 024 025 /** 026 * Creates a new database of books stored in a file named "books.xml" 027 * 028 * @param directory 029 * The directory in which the data file will reside 030 * @throws IOException 031 * If the data file cannot be created 032 * @throws JAXBException 033 * If we cannot create the XML context 034 */ 035 @Inject 036 public BookDatabase( @DataDirectory File directory ) throws JAXBException, IOException 037 { 038 this( directory, "books.xml" ); 039 } 040 041 /** 042 * Creates a new database of books. This method is package-protected for testing purposes. 043 * 044 * @param directory 045 * The directory in which the data file will reside 046 * @param fileName 047 * The name of the file to store the book inventory 048 * 049 * @throws IOException 050 * If the data file cannot be created 051 * @throws JAXBException 052 * If we cannot create the XML context 053 */ 054 BookDatabase( File directory, String fileName ) throws IOException, JAXBException 055 { 056 super( directory, fileName, BookDatabase.XmlBookDatabase.class, 057 BookDatabase.XmlBookDatabase.BookCount.class, Book.class ); 058 059 XmlBookDatabase xml = (XmlBookDatabase) readFile(); 060 if (xml != null) { 061 this.inventory = xml.getMap(); 062 } else { 063 this.inventory = new HashMap<Book, AtomicInteger>(); 064 } 065 } 066 067 public synchronized void remove( Book book ) 068 { 069 AtomicInteger count = inventory.get(book); 070 if (count == null || count.get() == 0) { 071 throw new IllegalStateException("We're out of " + book); 072 073 } else { 074 count.decrementAndGet(); 075 writeInventory(); 076 } 077 } 078 079 public synchronized void add(Book... books) { 080 for (Book book : books) { 081 AtomicInteger count = inventory.get(book); 082 if (count == null) { 083 count = new AtomicInteger( 0 ); 084 inventory.put(book, count); 085 } 086 count.incrementAndGet(); 087 } 088 089 writeInventory(); 090 } 091 092 private synchronized void writeInventory() 093 { 094 writeXml( new XmlBookDatabase( this.inventory) ); 095 } 096 097 public int getCopies( Book book ) 098 { 099 AtomicInteger count = inventory.get(book); 100 if (count == null) { 101 return 0; 102 103 } else { 104 return count.get(); 105 } 106 } 107 108 public Set<Book> getBooks() 109 { 110 return inventory.keySet(); 111 } 112 113 /** 114 * JAXB can't marshall a <code>HashMap</code>, so we need to use this stupid class to represent a book database. 115 */ 116 @XmlRootElement(name="book-database") 117 private static class XmlBookDatabase 118 { 119 @XmlElementWrapper(name="books") 120 private List<BookCount> counts; 121 122 123 /** 124 * For unmarshalling 125 */ 126 public XmlBookDatabase() { 127 128 } 129 130 public XmlBookDatabase( Map<Book, AtomicInteger> inventory ) 131 { 132 counts = new ArrayList<BookCount>(inventory.size()); 133 for (Map.Entry<Book, AtomicInteger> count : inventory.entrySet()) { 134 counts.add(new BookCount(count.getKey(), count.getValue())); 135 } 136 } 137 138 public Map<Book, AtomicInteger> getMap() 139 { 140 Map<Book, AtomicInteger> map = new HashMap<Book, AtomicInteger>(counts.size()); 141 for (BookCount count : counts) { 142 Book book = count.getBook(); 143 AtomicInteger ai = map.get(book); 144 if (ai == null) { 145 ai = new AtomicInteger( 0 ); 146 map.put(book, ai); 147 } 148 149 ai.addAndGet( count.getCount() ); 150 } 151 return map; 152 } 153 154 @XmlRootElement(name="count") 155 private static class BookCount 156 { 157 @XmlElement 158 private Book book; 159 160 @XmlAttribute 161 private int count; 162 163 /** 164 * For unmarshalling 165 */ 166 public BookCount() { 167 168 } 169 170 public BookCount( Book book, AtomicInteger count ) 171 { 172 this.book = book; 173 this.count = count.get(); 174 } 175 176 public Book getBook() 177 { 178 return book; 179 } 180 181 public int getCount() 182 { 183 return count; 184 } 185 } 186 } 187}