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