001    /* PAVLOV -- Multiple Choice Study System
002     * Copyright (C) 2000 - 2004 T.J. Willis
003     * 
004     * This program is free software; you can redistribute it and/or
005     * modify it under the terms of the GNU General Public License
006     * as published by the Free Software Foundation; either version 2
007     * of the License, or (at your option) any later version.
008     *      
009     * This program is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012     * GNU General Public License for more details.
013     *          
014     * You should have received a copy of the GNU General Public License
015     * along with this program; if not, write to the Free Software
016     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
017     *  
018     * $Header: /cvsroot/pavlov/net/sourceforge/pavlov/library/Book.java,v 1.10 2004/07/01 05:50:20 tj_willis Exp $
019     */ 
020    package net.sourceforge.pavlov.library;
021    
022    import java.util.*;
023    import javax.swing.tree.DefaultMutableTreeNode;
024    import java.io.*;
025    import java.nio.charset.*;
026    import java.net.URLEncoder;
027    import org.apache.log4j.*;
028    import org.xml.sax.*;
029    import org.xml.sax.helpers.*;
030    
031    /**
032     * Describes a Book, which is a collection of Chapters.
033     *
034     * @author <a href="mailto:tj_willis@users.sourceforge.net"></a>
035     * @version 1.0
036     * @see Library
037     * @see Chapter
038     * @see Question
039     * @has 1 Has 1..n net.sourceforge.pavlov.library.AbstractChapter
040      * @has 1 Has 1 java.io.File
041     */
042    public final class Book extends LibraryDocument
043                            implements AbstractBook, Serializable, Comparable
044    {
045            /** Filename of a descriptive image of this book.*/
046            private String coverFile;
047            /** Contains the chapters.*/
048            private TreeMap<String,AbstractChapter> chapters;
049    
050            /** Create an unnamed, empty book.*/
051            public Book() {
052                    name = "Unknown";
053                    init();
054            }
055    
056            private void init(){
057                    chapters = new TreeMap<String,AbstractChapter>();
058                    author = "Unknown";
059                    description = "Unknown";
060            }
061    
062            public Book(String name){
063                    this.name = name;
064                    init();
065            }
066            
067            public int compareTo(Object obj){
068                    Book b = (Book)obj;
069                    return getName().compareTo(b.getName());        
070            }
071            /**
072             * Creates an unnamed book with one unnamed chapter, which has one blank question.
073             *
074             * @return a <code>Book</code> value
075             */
076            public static Book makeBlankBook()
077            {
078                    Book b = new Book();
079                    Chapter c = Chapter.makeBlankChapter();
080                    b.addChapter(c);
081                    return b;
082            }
083    
084            /**
085             * Sets  filename of a descriptive image of this book.
086             * @param n a <code>String</code> value
087             */
088            public void setCover(final String n) {
089                    coverFile = n;
090            }
091            /**
092             * Returns filename of a descriptive image of this book.
093             * @return a <code>String</code> value
094             */
095            public String getCover() {
096                    return coverFile;
097            }
098    
099            /**
100             * Describe <code>getTitle</code> method here.
101             *
102             * @return a <code>String</code> value
103             * @deprecated use getName() instead.
104             */
105            @Deprecated public String getTitle() {
106                    return getName();
107            }
108    
109            /**
110             * Adds the named chapter, keyed on Chapter.getName().
111             * @param b a <code>Chapter</code> value
112             */
113            public void addChapter(final Chapter chapter) {
114                    chapters.put(chapter.getName(), chapter);
115            }
116    
117            /**
118             * Deletes the named chapter
119             * @param cpName a <code>String</code> value
120             */
121            public void deleteChapter(final String cpName) {
122                    if (cpName == null) return;
123                    Object o = chapters.remove(cpName);
124            }
125    
126    
127            /**
128             * Returns the named chapter, or null if it doesn't exist
129             * @param cpName a <code>String</code> value
130             * @return a <code>Chapter</code> value
131             */
132            public Chapter getChapter(final String cpName) {
133                    if (cpName == null) return (Chapter) null;
134                    return (Chapter) chapters.get(cpName);
135            }
136    
137    
138            /**
139             * Sums the number of questions of all my chapters and returns the total.
140             * @return an <code>int</code> value
141             */
142            public int getNumberOfQuestions() {
143                    int n = 0;
144                    Collection<AbstractChapter> cv = chapters.values();
145    for(AbstractChapter da : cv) {
146                            n += da.getNumberOfQuestions();
147                    }
148                    return n;
149            }
150    
151            /**
152             * Dumps the Book in XML format to the given writer.  
153             * Loops throught chapters, questions.
154             * @param writer a <code>java.io.Writer</code> value
155             * @exception java.io.IOException if an error occurs
156             */
157            public void toXML(java.io.Writer writer)
158            throws java.io.IOException {
159                    Collection<AbstractChapter> cv = chapters.values();
160    
161                    writer.write("<BOOK NAME=\"" + name + "\" AUTHOR=\"" + author + "\" COVER=\"" + coverFile + "\">\n");
162                    writer.write("\t<CONTENTS>" + description + "</CONTENTS>\n");
163    for(AbstractChapter da : cv) {
164                            da.toXML(writer);
165                    }
166                    writer.write("</BOOK>\n");
167            }
168    
169            /**
170             * Saves the book in the given directory, using the book's cannonical
171             * filename.
172             *
173             * @param directory a <code>File</code> value
174             */
175            public void save(File directory)
176            throws IOException
177            {
178                    File outputFile = new File(directory,getBaseFileName()+".xml");
179                    saveAs(outputFile);
180            }
181    
182            /**
183             * Saves the book to the given file.
184             *
185             * @param file a <code>File</code> value
186             */
187            public void saveAs(File file)
188            throws IOException
189            {
190                    Writer out = null;
191                    try {
192                            FileOutputStream fos = new FileOutputStream(file);
193                            Charset utf8 = Charset.forName("UTF-8");
194                            //OutputStreamWriter wrt = new OutputStreamWriter(fos,utf8);
195                            out = new BufferedWriter(new OutputStreamWriter(fos,utf8));
196                            //System.out.println("got utf-8 writer");
197                    } catch (Exception ex) {
198                            System.out.println("Cannot encode to UTF-8");
199                            out = new FileWriter(file);
200                    }
201                    toXML(out);
202                    out.flush();
203            }
204    
205            public static Book loadFrom(String filename)
206            {
207                    File f = new File(filename);
208                    return loadFrom(f);
209            }
210    
211            public static Book loadFrom(File bookFile)
212            {
213                    if(bookFile==null)
214                    {
215                            //showError("Cannot open file",null);
216                            return null;
217                    }
218                    String fname = bookFile.getName();
219                    if(!bookFile.exists())
220                    {
221                            //showError("File " + fname + " doesn't exist",null);
222                            return null;
223                    }
224                    if(!bookFile.isFile())
225                    {
226                            //showError("File " + fname + " is not a book file",null);
227                            return null;
228                    }
229                    if(!bookFile.canRead())
230                    {
231                            //showError("File " + fname + " is read-protected",null);
232                            return null;
233                    }
234    
235                    BookXMLHandler br = new BookXMLHandler();
236                    XMLReader xr = null;
237                    Book b = null;
238                    try {
239                            xr = XMLReaderFactory.createXMLReader();
240                    } catch (Exception e) {
241                            //showError("Cannot Create XML Reader",e);
242                            return null;
243                    }
244    
245                    xr.setContentHandler(br);
246    
247                    try {
248                            //HERE
249                            /*
250                            FileReader fr = new FileReader(bookFile);
251                            InputSource is = new InputSource(fr);
252                            is.setEncoding("UTF8");
253                            */
254    
255                            Reader in = null;
256                            //try {
257                            FileInputStream fis = new FileInputStream(bookFile);
258                            Charset utf8 = Charset.forName("UTF-8");
259                            //OutputStreamWriter wrt = new OutputStreamWriter(fos,utf8);
260                            in = new BufferedReader(new InputStreamReader(fis,utf8));
261                            InputSource is = new InputSource(in);
262                            //System.out.println("got utf-8 reader");
263                            //} catch (Exception ex) {
264                            //      System.out.println("Cannot decode from UTF-8");
265                            //in = new File(file);
266                            //}
267    
268                            xr.parse(is);
269                    } catch (Exception e) {
270                            //showError("Cannot Parse File " + fname,e);
271                            return null;
272                    }
273    
274                    //System.out.println("file name = " + fname.toString());
275                    b = br.getBook();
276    
277                    if(b==null) System.out.println("Book is null");//^
278                    return b;
279            }
280    
281            /**
282             * Creates a cannonical filename for this book, lowercasing it and
283             * URLEncoding it.
284             *
285             * @return a <code>String</code> value
286             */
287            protected String getBaseFileName()
288            {
289                    String n = getName().toLowerCase();
290                    try {
291                            n = URLEncoder.encode(n,"UTF-8");
292                    } catch (Exception e) {}
293                    return n;
294            }
295    
296    
297            /**
298             * Displays chapters as a tree.  
299             * @param bk a <code>DefaultMutableTreeNode</code> value
300             */
301            public void populateTree(DefaultMutableTreeNode bk) {
302                    DefaultMutableTreeNode chapter = null;
303    
304                    String x;
305                    Collection<AbstractChapter> cv = chapters.values();
306                    for(AbstractChapter cp : cv) {
307                            chapter = new DefaultMutableTreeNode(cp);
308                            bk.add(chapter);
309                    }
310            }
311    
312            /**
313             * Returns a hashtable of all my chapters.
314             *
315             * @return a <code>Hashtable</code> value
316             */
317            public TreeMap<String, AbstractChapter> getChapters()
318            {
319                    return chapters;
320            }
321    
322            /**
323             * Returns the number of chapters I have.
324             *
325             * @return an <code>int</code> value
326             */
327            public int getNumberOfChapters()
328            {
329                    return chapters.size();
330            }
331    
332            /**
333             * Returns the book's name
334             * @return a <code>String</code> value
335             */
336            public String toString() {
337                    return name;
338            }
339    
340            /**
341             * Returns the book's chapters as a collection. 
342             * @return a <code>Collection</code> value
343             */
344            public Collection<AbstractChapter> getValues() {
345                    final boolean mode = true;
346                    if(mode){
347                            return new TreeSet<AbstractChapter>(chapters.values());//java.util.Collections.sort(ls);
348                    } else {
349                            return chapters.values();
350                    }
351            }
352    
353    
354            /**
355             * Returns the book's chapters as a read-only collection. 
356             * @return a <code>Collection</code> value
357             */
358            public Collection<AbstractChapter> getChaptersReadOnly()
359            {
360                    return Collections.unmodifiableCollection(getValues());
361            }
362    
363    
364            //     /**
365            //      * Returns the book's chapters as a collection.
366            //      * @return a <code>Collection</code> value
367            //      */
368            //     public Collection<AbstractChapter> getChapters() {
369            //      return chapters.values();
370            //     }
371    
372            /**
373             * Returns true if this book equals the given book.
374             *
375             * @param obj an <code>Object</code> value
376             * @return a <code>boolean</code> value
377             */
378            public boolean equals(final Object obj)
379            {
380                    if(obj==null && this!=null) return false;
381                    if(obj!=null && this==null) return false;
382                    if(obj==null && this==null) return true;
383    
384                    if( !(obj instanceof Book) ) return false;
385                    Book q = (Book) obj;
386    
387                    if( getName()==null && q.getName()!=null) return false;
388                    if( getAuthor()==null && q.getAuthor()!=null) return false;
389                    if( getCover()==null && q.getCover()!=null) return false;
390                    if( getDescription()==null && q.getDescription()!=null) return false;
391                    if( getChapters()==null && q.getChapters()!=null) return false;
392    
393                    // FIXED: use .equals() instead of ==
394                    if(getName()!=null)
395                            if( ! getName().equals(q.getName())) return false;
396                    if(getAuthor()!=null)
397                            if( !getAuthor().equals(q.getAuthor())) return false;
398                    if(getCover()!=null)
399                            if( !getCover().equals(q.getCover())) return false;
400                    if(getDescription()!=null)
401                            if( ! getDescription().equals(q.getDescription())) return false;
402                    if(getChapters()!=null)
403                            if( ! getChapters().equals(q.getChapters())) return false;
404    
405                    return true;
406            }
407    
408            /**
409             * Uses a neat trick from "Design Patterns in Java" to make a completely
410             * independent copy of this book.  Normal copying methods are unsuitable
411             * for book editing.
412             *
413             * @return a <code>Book</code> value
414             */
415            public Book deepCopy()
416            {
417                    try {
418                            ByteArrayOutputStream b = new ByteArrayOutputStream();
419                            ObjectOutputStream out = new ObjectOutputStream(b);
420                            out.writeObject(this);
421                            ByteArrayInputStream bIn = new ByteArrayInputStream(b.toByteArray());
422                            ObjectInputStream oi = new ObjectInputStream(bIn);
423                            return ( (Book)oi.readObject());
424                    }
425                    catch (Exception e)
426                    {
427                            return null;
428                    }
429            }
430    
431    }
432    
433    
434    
435    
436