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}