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