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    }