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/main/Quiz.java,v 1.8 2004/07/01 09:03:43 tj_willis Exp $ 019 */ 020 package net.sourceforge.pavlov.main; 021 022 //import net.sourceforge.pavlov.feedback.*; 023 import java.util.Vector; 024 import net.sourceforge.pavlov.event.*; 025 import net.sourceforge.pavlov.library.*; 026 import net.sourceforge.pavlov.user.*; 027 import org.apache.log4j.*; 028 029 /** 030 * Contains state information for a quiz in progress. 031 * 032 * @author <a href="mailto:tj_willis@users.sourceforge.net">T.J. Willis</a> 033 * @version 1.0 034 */ 035 public class Quiz 036 implements StrategyListener, AnswerListener, AnswerEventProducer { 037 // unit coverage in TestQuiz.java 038 private AbstractLibrary library; 039 private AbstractPavlovApplication abstractPavlov; 040 private BookData bookData; 041 private Chapter chapter; 042 private ChapterData chapterData; 043 private Question question; 044 private QuestionData questionData; 045 private QuizData quizData; 046 private User user; 047 private Vector < QuestionChangedListener > questionChangedListeners; 048 private boolean firstQuestion = true; 049 private static Category cat 050 = Category.getInstance(DefaultTemplateKit.class.getName()); 051 /** 052 * The chapter to choose questions from. 053 * 054 */ 055 protected ChapterReference chapRef; 056 /** 057 * Components, mostly pluglets, that respond to users answers. 058 * 059 */ 060 protected Vector < AnswerListener > answerListeners; 061 062 063 /** 064 * Creates a new <code>Quiz</code> instance. The AbstractPavlovApplication 065 * may safely be null. 066 * 067 * @param absPav an <code>AbstractPavlovApplication</code> value 068 * @param aUser an <code>User</code> value 069 * @param ref a <code>ChapterReference</code> value 070 */ 071 public Quiz (AbstractPavlovApplication absPav, User aUser, 072 ChapterReference ref) { 073 074 abstractPavlov = absPav; 075 user = aUser; 076 library = ref.getLibrary (); 077 //bookName = _bookName; 078 chapRef = ref; 079 080 chapter = ref.getChapter (); //library.getChapter(bookName, chapterName); 081 // FIXME: implement user.getBookData(ChapterReference) 082 bookData = user.getBookData (ref.getBookName ()); 083 // FIXME: implement user.getChapterData(ChapterReference) 084 chapterData = 085 user.getChapterData (ref.getBookName (), ref.getChapterName ()); 086 087 quizData = new QuizData (); 088 chapterData.addQuizData (quizData); 089 090 questionChangedListeners = new Vector < QuestionChangedListener > (); 091 answerListeners = new Vector < AnswerListener > (); 092 if (abstractPavlov != null) 093 abstractPavlov.registerFeedbackPlugins (this); 094 095 //numAnswers = 0; 096 //nRights = 0; 097 } 098 099 /** 100 * Describe <code>getChapterReference</code> method here. 101 * 102 * @return a <code>ChapterReference</code> value 103 */ 104 public ChapterReference getChapterReference () { 105 return chapRef; 106 } 107 108 /** 109 * Describe <code>getUserName</code> method here. 110 * 111 * @return a <code>String</code> value 112 */ 113 public String getUserName () { 114 return user.getName (); 115 } 116 117 /** 118 * Returns the active user. 119 * 120 * @return a <code>String</code> value 121 */ 122 public User getUser () { 123 return user; 124 } 125 126 127 /** 128 * Adds an object to be notified when the question has changed. 129 * 130 * @param lis a <code>QuestionChangedListener</code> value 131 */ 132 public void addQuestionChangedListener (QuestionChangedListener lis) { 133 questionChangedListeners.add (lis); 134 } 135 136 /** 137 * Ask the user a new question. 138 * 139 * @return a <code>Question</code> value 140 */ 141 public Question newQuestion () { 142 assert chapterData != null:"ChapterData is null"; 143 assert chapter != null:"Chapter is null"; 144 145 question = getNextQuestion (); 146 String qid = question.getID (); 147 questionData = chapterData.getQuestionData (qid); 148 149 assert question != null:"No Questions To Ask!"; 150 151 notifyQuestionChangedListeners (); 152 return question; 153 } 154 155 //======================================================================== 156 //Candidates for changes in client-server 157 158 /** 159 * Let registerd answer listeners know that the question has changed. 160 * 161 */ 162 protected void notifyQuestionChangedListeners () { 163 // FIXED: use iterator 164 for (QuestionChangedListener lis:questionChangedListeners) { 165 lis.questionChanged (question, questionData); 166 } 167 } 168 169 /** 170 * Saves the user's statistical data. 171 * 172 * @exception java.io.FileNotFoundException if an error occurs 173 * @exception java.io.IOException if an error occurs 174 */ 175 public void save () 176 throws java.io.FileNotFoundException, java.io.IOException { 177 user.save (); //abstractPavlov.saveUsers(); 178 } 179 180 /** 181 * Tell quizData and chapterData that the user answered a question 182 * correctly. 183 * 184 */ 185 protected void rightAnswer () { 186 //nRights++; 187 quizData.addRight (); 188 chapterData.addRight (question.getID ()); 189 } 190 191 /** 192 * Tell quizData and chapterData that the user answered a question 193 * incorrectly. Delegate to abstractPavlov to inform the user. 194 * 195 */ 196 protected void wrongAnswer () { 197 quizData.addWrong (); 198 if (abstractPavlov != null){ 199 String fmt = "The correct answer was: %1s.";//^ 200 abstractPavlov.showIncorrectDialog (String.format(fmt,question.getRightAnswer ())); 201 } 202 chapterData.addWrong (question.getID ()); 203 } 204 205 /** 206 * Let the registered answerlisteners know that the user has answered 207 * a question. 208 * 209 * @param correct a <code>boolean</code> value 210 */ 211 protected void notifyAnswerListeners (final boolean correct) { 212 AnswerEvent ae = new AnswerEvent (correct, user, questionData, 213 quizData,bookData, chapterData, 214 firstQuestion); 215 for (AnswerListener ev:answerListeners) { 216 try { 217 ev.answerEvent (ae); 218 } catch (Exception ex) { 219 cat.warn("Error sending answerevent",ex); 220 } 221 } 222 } 223 224 /** 225 * Return the current ChapterData. 226 * 227 * @return a <code>ChapterData</code> value 228 */ 229 public ChapterData getChapterData () { 230 return chapterData; 231 } 232 233 /** 234 * Gets a valud question using the user's current question selection 235 * strategy. 236 * 237 * @return a <code>Question</code> value 238 */ 239 protected Question getNextQuestion () { 240 assert user!=null : "User is null in getNextQuestion"; 241 return user.getValidQuestion (chapterData, chapter); 242 } 243 244 //======================================================================== 245 246 /** 247 * The user has answered a question. Handle it. 248 * 249 * @param e an <code>AnswerEvent</code> value 250 */ 251 public void answerEvent (AnswerEvent e) { 252 if(e==null) throw new IllegalArgumentException("Answer Event is null"); 253 //progress.setData(percentageListener.getHistory()); 254 //numAnswers++; 255 256 if (e.isCorrect ()) { 257 rightAnswer (); 258 } else { 259 wrongAnswer (); 260 } 261 notifyAnswerListeners (e.isCorrect ()); 262 newQuestion (); 263 // FIXED: i have mixed feelings about doing GC here 264 // on one hand, it looks useful, on the other hand, it's 265 // an antipattern... My jUnit test of 100 answerEvents 266 // went from 11 seconds to 1 second when I took out the GC 267 // code. Also, tiger claims to make explicit GC's unnecessary... 268 //System.runFinalization(); -- put this back in? 269 //System.gc(); // pluglets not cleaning memory 270 firstQuestion = false; 271 try { 272 save(); 273 } catch (Exception ex) { 274 cat.error("Error: Exception saving user file",ex); 275 } 276 } 277 278 /** 279 * Choose the question selection strategy. 280 * 281 * @param s an <code>AbstractStrategy</code> value 282 */ 283 public void setStrategy (AbstractStrategy s) { 284 assert chapterData != null:"ChapterData is null"; 285 chapterData.setStrategy (s); 286 } 287 288 /** 289 * Implements the StrategyListener interface. 290 * 291 * @param which an <code>AbstractStrategy</code> value 292 */ 293 public void strategyChanged (AbstractStrategy which) { 294 setStrategy (which); 295 } 296 297 /** 298 * Register an AnswerListener with this quiz. 299 * 300 * @param g an <code>AnswerListener</code> value 301 */ 302 public void addAnswerListener (AnswerListener g) { 303 answerListeners.add (g); 304 } 305 306 }