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 }