001    /*
002     * Copyright (C) 2008 Adam Cornett This program is free software; you can
003     * redistribute it and/or modify it under the terms of the GNU General Public
004     * License as published by the Free Software Foundation; either version 3 of the
005     * License, or (at your option) any later version. This program is distributed
006     * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
007     * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
008     * See the GNU General Public License for more details. You should have received
009     * a copy of the GNU General Public License along with this program; if not, see
010     * <http://www.gnu.org/licenses>.
011     */
012    package net.codescore.managers;
013    
014    import java.util.HashMap;
015    import java.util.Iterator;
016    import java.util.LinkedList;
017    import java.util.List;
018    import java.util.Map;
019    import java.util.concurrent.ConcurrentHashMap;
020    import java.util.concurrent.ConcurrentLinkedQueue;
021    
022    import org.apache.cayenne.DataObjectUtils;
023    import org.apache.cayenne.ObjectContext;
024    import org.apache.cayenne.access.DataContext;
025    import org.apache.cayenne.query.NamedQuery;
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    
029    import net.codescore.dbo.Competition;
030    import net.codescore.dbo.Problem;
031    import net.codescore.dbo.ProblemStatus;
032    import net.codescore.dbo.Submission;
033    import net.codescore.dbo.Team;
034    import net.codescore.dbo.TeamSubmission;
035    import net.codescore.exe.ExecutionResult;
036    import net.codescore.exe.GradingMode;
037    import net.codescore.exe.GradingThread;
038    import net.codescore.exe.ScoringMode;
039    import net.codescore.ui.SubUpdateListener;
040    import net.codescore.ui.client.TeamHome;
041    
042    /**
043     * Manages a competition, providing methods to get competitions from the
044     * database, and facilities for processing submissions, such as a grading queue
045     * and accessor methods to the competition settings.
046     * 
047     * @author Adam Cornett
048     */
049    public class CompetitionManager implements SubUpdateListener {
050            /**
051             * The last competition to be created
052             * 
053             * @deprecated
054             */
055            private static CompetitionManager activeCompetition;
056            private static DataContext dc;
057            /**
058             * A map of linking a competition to its associated manager, used to prevent
059             * a competition from having more than one associated manager
060             */
061            private static HashMap<Competition, CompetitionManager> managers =
062                    new HashMap<Competition, CompetitionManager>();
063    
064            /**
065             * @return A list of all competitions, using the internal
066             *         {@link DataContext}
067             */
068            public static List<Competition> getAllComps() {
069                    if (dc == null)
070                            dc = DataContext.createDataContext();
071                    return getAllComps(dc);
072            }
073    
074            /**
075             * @param oc
076             *            The context used to execute the query
077             * @return A List of all competitions
078             */
079            public static List<Competition> getAllComps(ObjectContext oc) {
080                    NamedQuery nq = new NamedQuery("competitions_all");
081                    return oc.performQuery(nq);
082            }
083    
084            /**
085             * @param c
086             *            The context used to search for the competition
087             * @param name
088             *            The name of the competition
089             * @return The competition with the associated name
090             */
091            public static Competition getCompetitionByName(ObjectContext c, String name) {
092                    HashMap<String, Object> params = new HashMap<String, Object>();
093                    params.put("n", name);
094                    NamedQuery competitionByName =
095                            new NamedQuery("competition_by_name", params);
096                    List l = c.performQuery(competitionByName);
097                    if (l.size() < 1)
098                            throw new RuntimeException("Couldn't find competition " + name);
099                    else if (l.size() > 1)
100                            throw new RuntimeException("Multiple choices");
101                    return (Competition) l.get(0);
102            }
103    
104            /**
105             * Uses the internal context to search for a competition
106             * 
107             * @param name
108             *            The name of the competition to lookup
109             * @return The competition with the given name
110             */
111            public static Competition getCompetitionByName(String name) {
112                    if (dc == null)
113                            dc = DataContext.createDataContext();
114                    return getCompetitionByName(dc, name);
115            }
116    
117            /**
118             * @return The last manager associated with the most recently created
119             *         competition
120             * @deprecated
121             */
122            public static CompetitionManager getCurrent() {
123                    if (activeCompetition == null) {
124                            ObjectContext oc = DataContext.createDataContext();
125                            new CompetitionManager(getCompetitionByName(oc, "Test Competition"));
126    
127                    }
128                    return activeCompetition;
129            }
130    
131            /**
132             * @param oc
133             *            The context used to lookup a competition
134             * @return The last registered competition
135             * @deprecated
136             */
137            public static Competition getCurrent(ObjectContext oc) {
138                    if (activeCompetition == null)
139                            activeCompetition =
140                                    new CompetitionManager(getCompetitionByName(oc,
141                                            "Test Competition"));
142                    if (activeCompetition.getCompetition().getObjectContext().equals(oc))
143                            return activeCompetition.getCompetition();
144                    else
145                            return (Competition) DataObjectUtils.objectForPK(oc,
146                                    activeCompetition.getCompetition().getObjectId());
147            }
148    
149            /**
150             * Get all currently running competitions using the internal context
151             * 
152             * @return A list of all competitions that are currently 'running': The
153             *         competitions start time is before the current time, and the end
154             *         time is after the current time.
155             */
156            public static List<Competition> getCurrentComps() {
157                    return getCurrentComps(DataContext.createDataContext());
158            }
159    
160            /**
161             * A list of all currently running competitions using the supplied context
162             * 
163             * @param oc
164             *            The context to use when executing the query
165             * @return A list of all competitions where the competition's start time is
166             *         before the current time and its end time is after the current
167             *         time.
168             */
169            public static List<Competition> getCurrentComps(ObjectContext oc) {
170                    NamedQuery nq = new NamedQuery("competitions_current");
171                    return oc.performQuery(nq);
172            }
173    
174            /**
175             * Get the associated manager for a competition. A new manager will be
176             * created if one does not already exist for the given competition.
177             * 
178             * @param c
179             *            The competition
180             * @return The manager associated with the competition.
181             */
182            public static CompetitionManager getManager(Competition c) {
183                    if (managers.containsKey(c))
184                            return managers.get(c);
185                    return new CompetitionManager(c);
186    
187            }
188    
189            /**
190             * Lookup a submission for a given submission id (sid)
191             * 
192             * @param c
193             *            The context to use for executing the query
194             * @param sid
195             *            The sid of the submission
196             * @return The Team Submission with the associated sid
197             */
198            public static TeamSubmission getSubmission(ObjectContext c, int sid) {
199                    HashMap<String, Object> params = new HashMap<String, Object>();
200                    params.put("sid", sid);
201                    NamedQuery subBySID = new NamedQuery("sub_by_sid", params);
202                    List l = c.performQuery(subBySID);
203                    if (l.size() < 1)
204                            throw new RuntimeException("Couldn't find submission " + sid);
205                    else if (l.size() > 1)
206                            throw new RuntimeException("Multiple choices");
207                    return (TeamSubmission) l.get(0);
208            }
209    
210            private volatile boolean compIsRunning;
211            private Competition currentComp;
212            private ConcurrentHashMap<Submission, ExecutionResult> gradeResults;
213            private ConcurrentLinkedQueue<Submission> gradingQueue, practiceQueue;
214            private List<GradingThread> gThreads;
215            private ConcurrentLinkedQueue<Submission> judgingQueue;
216            private Log log = LogFactory.getLog(CompetitionManager.class);
217            private int numGradingThreads;
218            private Map<Team, TeamHome> teamHomes;
219            private GradingThread testThread;
220    
221            /**
222             * Create a new manager.
223             * 
224             * @param c
225             *            The competition the manager is associated with
226             * @throws IllegalArgumentException
227             *             If a manager already exists for the competition. Use
228             *             {@link CompetitionManager#getManager(Competition)} to safely
229             *             get a manager for a competition
230             */
231            public CompetitionManager(Competition c) {
232                    if (managers.containsKey(c)) {
233                            throw new IllegalArgumentException("A manager for " + c
234                                    + " already exists, please use getManager(Competition)");
235                    }
236                    managers.put(c, this);
237                    if (activeCompetition == null) {
238                            CompetitionManager.activeCompetition = this;
239                    }
240                    gradingQueue = new ConcurrentLinkedQueue<Submission>();
241                    practiceQueue = new ConcurrentLinkedQueue<Submission>();
242                    judgingQueue = new ConcurrentLinkedQueue<Submission>();
243                    gradeResults = new ConcurrentHashMap<Submission, ExecutionResult>();
244                    gThreads = new LinkedList<GradingThread>();
245                    teamHomes = new HashMap<Team, TeamHome>();
246                    this.setCurrentComp(c);
247                    compIsRunning = currentComp.getActive();
248                    SubmissionListener.registerCallback(this);
249                    if (getCompetition().getProp("num_graders") == null) {
250                            numGradingThreads = 10; // Default number of grading threads
251                    } else {
252                            Integer i = (Integer) getCompetition().getProp("num_graders");
253                            if (i == null)
254                                    numGradingThreads = 2;
255                            else
256                                    numGradingThreads = i;
257                    }
258                    testThread = new GradingThread(this, GradingMode.PracticeSubmission);
259                    testThread.start();
260                    log.debug("Loaded:" + currentComp);
261            }
262    
263            /**
264             * When a submission has been graded, the result of the Grader is returned
265             * here so that it can be reviewed by a judge.
266             * 
267             * @param s
268             *            The submission which was graded
269             * @param er
270             *            The result of the grading
271             */
272            public void addGradeResult(Submission s, ExecutionResult er) {
273                    // If it is not a Judge's Solution, add the result to the list
274                    if (s instanceof TeamSubmission)
275                            gradeResults.put(s, er);
276                    // If the status is still pending, send it to a judge
277                    if (s.getStatus() == null) {
278                            judgingQueue.add(s);
279                    }
280                    s.getObjectContext().commitChanges();
281            }
282    
283            /**
284             * Add a submission to the grading queue.<br />
285             * If the queue already contains s, it will not be added again. The queue is
286             * a FIFO queue.
287             * 
288             * @see ConcurrentLinkedQueue
289             * @param s
290             *            The submission to be added.
291             */
292            public void addSubmissionToGradeQueue(Submission s) {
293                    // Don't add a submission if its already in the queue
294                    if (gradingQueue.contains(s))
295                            return;
296                    log.info("Adding Submission to grading queue:" + s.getObjectId());
297                    gradingQueue.add(s);
298                    checkGThreads();
299            }
300    
301            /**
302             * Add a submission to the practice queue.<br />
303             * If the queue already contains s, it will not be added again. The queue is
304             * a FIFO queue.
305             * 
306             * @see ConcurrentLinkedQueue
307             * @param s
308             *            The submission to be added.
309             */
310            public void addSubmissionToPracticeQueue(Submission s) {
311                    // Don't add a submission if its already in the queue
312                    if (practiceQueue.contains(s))
313                            return;
314                    log.info("Adding Submission to practice queue:" + s.getObjectId());
315                    practiceQueue.add(s);
316            }
317    
318            /**
319             * Removes dead graders from the list and ensures that the actual number of
320             * active grading threads matches the expected number.
321             */
322            public void checkGThreads() {
323                    checkGThreads(true);
324            }
325    
326            /**
327             * Dump debugging information to the log
328             */
329            public void debug() {
330                    log.debug(getCompetition());
331                    getCompetition().debug();
332            }
333    
334            /**
335             * Check for alive graders.
336             * 
337             * @return The number of alive grading threads
338             */
339            public int getAliveGThreads() {
340                    int val = 0;
341                    for (GradingThread gt : gThreads)
342                            if (gt.isAlive())
343                                    val++;
344                    return val;
345            }
346    
347            /**
348             * @return true if the system should mark submissions as having a compile
349             *         error when the compiler returns a non-zero value.
350             */
351            public boolean getAutoCompileError() {
352                    Boolean b = (Boolean) getCompetition().getProp("auto_correct");
353                    if (b != null && b) {
354                            return true;
355                    }
356                    return false;
357            }
358    
359            /**
360             * Check to see if the system will mark submissions correct when they match
361             * the expected output exactly.
362             * 
363             * @return true if the system should mark submissions correct if they have a
364             *         perfect diff.
365             */
366            public boolean getAutoCorrect() {
367                    Boolean b = (Boolean) getCompetition().getProp("auto_correct");
368                    if (b != null && b) {
369                            return true;
370                    }
371                    return false;
372            }
373    
374            /**
375             * @return true if the system should mark submissions as having a runtime
376             *         error if they do not exit with a exit code of 0;
377             */
378            public boolean getAutoRuntimeError() {
379                    Boolean b = (Boolean) getCompetition().getProp("auto_runtime_error");
380                    if (b != null && b) {
381                            return true;
382                    }
383                    return false;
384            }
385    
386            /**
387             * @return The associated competition
388             */
389            public Competition getCompetition() {
390                    return this.currentComp;
391            }
392    
393            /**
394             * @return The <code>compile_error_status</code> property of the
395             *         competition.
396             * @see #getCompileError(ObjectContext)
397             */
398            public int getCompile_error_status() {
399                    Integer ces =
400                            (Integer) getCompetition().getProp("compile_error_status");
401                    return ces;
402            }
403    
404            /**
405             * @param oc
406             *            The context used to get the ProblemStatus.
407             * @return The problem status object signifying a compiler error for a
408             *         submission.
409             * @see #getCompile_error_status()
410             */
411            public ProblemStatus getCompileError(ObjectContext oc) {
412                    return DataObjectUtils.objectForPK(oc, ProblemStatus.class,
413                            getCompile_error_status());
414            }
415    
416            /**
417             * @param oc
418             *            The context used to get the ProblemStatus.
419             * @return The problem status object signifying a correct submission.
420             * @see #getCorrect_status()
421             */
422            public ProblemStatus getCorrect(ObjectContext oc) {
423                    return DataObjectUtils.objectForPK(oc, ProblemStatus.class,
424                            getCorrect_status());
425            }
426    
427            /**
428             * @return The <code>correct_status</code> property of the competition
429             * @see #getCorrect(ObjectContext)
430             */
431            public int getCorrect_status() {
432                    Integer cs = (Integer) getCompetition().getProp("correct_status");
433                    return cs;
434            }
435    
436            /**
437             * @return The maximum execution time for a submission in ms.
438             */
439            public int getExeTimeout() {
440                    return (Integer) getCompetition().getProp("exe_timeout");
441            }
442    
443            /**
444             * @return The number of grading threads this competition should maintain
445             */
446            public int getNumGraders() {
447                    Integer db = (Integer) currentComp.getProp("num_graders");
448                    if (db != numGradingThreads)
449                            numGradingThreads = db;
450                    return numGradingThreads;
451            }
452    
453            /**
454             * @return A list of submissions waiting to be graded
455             */
456            public List<Submission> getPendingSubs() {
457                    HashMap<String, Object> params = new HashMap<String, Object>();
458                    params.put("comp", getCompetition());
459                    NamedQuery pSubs = new NamedQuery("pending_subs", params);
460                    List<Submission> subs =
461                            getCompetition().getObjectContext().performQuery(pSubs);
462                    return subs;
463            }
464    
465            /**
466             * @return the <code>presentation_error_status</code> property of the
467             *         competition
468             * @see #getPresError(ObjectContext)
469             */
470            public int getPresentation_error_status() {
471                    Integer pes =
472                            (Integer) getCompetition().getProp("presentation_error_status");
473                    return pes;
474            }
475    
476            /**
477             * @param oc
478             *            The context used to get the ProblemStatus.
479             * @return The problem status object signifying a presentational error in a
480             *         submission
481             * @see #getPresentation_error_status()
482             */
483            public ProblemStatus getPresError(ObjectContext oc) {
484                    return DataObjectUtils.objectForPK(oc, ProblemStatus.class,
485                            getPresentation_error_status());
486            }
487    
488            /**
489             * @see ConcurrentLinkedQueue#size()
490             * @return The length of the grading queue
491             */
492            public int getQueueLength() {
493                    return gradingQueue.size();
494            }
495    
496            /**
497             * Returns the ExecutionResult for a given submission.
498             * 
499             * @param s
500             *            The submission you're looking for.
501             * @return The ExecutionResult from the grading of s.
502             */
503            public ExecutionResult getResult(TeamSubmission s) {
504                    return gradeResults.get(s);
505            }
506    
507            /**
508             * @return The <code>runtime_error_status</code> property of the
509             *         competition.
510             * @see #getRuntimeError(ObjectContext)
511             */
512            public int getRuntime_error_status() {
513                    Integer res =
514                            (Integer) getCompetition().getProp("runtime_error_status");
515                    return res;
516            }
517    
518            /**
519             * @param oc
520             *            The context used to get the ProblemStatus.
521             * @return The problem status object signifying a runtime error in a
522             *         submission.
523             * @see #getRuntime_error_status()
524             */
525            public ProblemStatus getRuntimeError(ObjectContext oc) {
526                    return DataObjectUtils.objectForPK(oc, ProblemStatus.class,
527                            getRuntime_error_status());
528            }
529    
530            /**
531             * Get the scoring mode for this competition.<br />
532             * Stored in the <code>scoring_mode</code> competition property
533             * 
534             * @return The scoring mode for the associated competition
535             */
536            public ScoringMode getScoringMode() {
537                    return (ScoringMode) getCompetition().getProp("scoring_mode");
538            }
539    
540            /**
541             * @return The <code>test_status</code> property of the competition.
542             * @see #getTestStatus(ObjectContext)
543             */
544            public int getTest_status() {
545                    Integer tes = (Integer) getCompetition().getProp("test_status");
546                    return tes;
547            }
548    
549            /**
550             * @param oc
551             *            The context used to get the ProblemStatus.
552             * @return The problem status signifying a problem should not be graded
553             *         normally, but instead is a test submission.
554             * @see #getTest_status()
555             */
556            public ProblemStatus getTestStatus(ObjectContext oc) {
557                    return DataObjectUtils.objectForPK(oc, ProblemStatus.class,
558                            getTest_status());
559            }
560    
561            /**
562             * @return The <code>timelimit_error_status</code> property of the
563             *         competition.
564             * @see #getTimelimitError(ObjectContext)
565             */
566            public int getTimelimit_error_status() {
567                    Integer tes =
568                            (Integer) getCompetition().getProp("timelimit_error_status");
569                    return tes;
570            }
571    
572            /**
573             * @param oc
574             *            The context used to get the ProblemStatus.
575             * @return The problem status signifying that a submission has exceeded the
576             *         maximum time limit.
577             * @see #getTimelimit_error_status()
578             * @see #getExeTimeout()
579             */
580            public ProblemStatus getTimelimitError(ObjectContext oc) {
581                    return DataObjectUtils.objectForPK(oc, ProblemStatus.class,
582                            getTimelimit_error_status());
583            }
584    
585            /**
586             * Tells the grading system if it should use the extra security precautions
587             * while executing a submission.<br />
588             * <b>This will cause the grader to fail currently as the security system
589             * has not been implemented</b>
590             * 
591             * @return The <code>use_security</code> property of the competition
592             */
593            public boolean getUseSecurity() {
594                    return (Boolean) getCompetition().getProp("use_security");
595            }
596    
597            /**
598             * @return The <code>wrong_output_status</code> property of the
599             *         competition
600             * @see #getWrongOutput(ObjectContext)
601             */
602            public int getWrong_output_status() {
603                    Integer wos = (Integer) getCompetition().getProp("wrong_output_status");
604                    return wos;
605            }
606    
607            /**
608             * @param oc
609             *            The context used to get the ProblemStatus.
610             * @return The problem status object signifying that a submission has the
611             *         wrong output.
612             * @see #getWrong_output_status()
613             */
614            public ProblemStatus getWrongOutput(ObjectContext oc) {
615                    return DataObjectUtils.objectForPK(oc, ProblemStatus.class,
616                            getWrong_output_status());
617            }
618    
619            /**
620             * True if the competition is 'running' which means teams can login and
621             * submit solutions.
622             * 
623             * @return the compIsRunning
624             */
625            public synchronized boolean isCompIsRunning() {
626                    return this.compIsRunning;
627            }
628    
629            @Override
630            public void onSubAdd(TeamSubmission s) {
631                    if (s.getStatus().equals(getTestStatus(s.getObjectContext())))
632                            addSubmissionToPracticeQueue(s);
633                    else if (s.getStatus() == null)
634                            addSubmissionToGradeQueue(s);
635            }
636    
637            @Override
638            public void onSubUpdate(TeamSubmission s) {
639                    return;
640            }
641    
642            /**
643             * Called by the grading thread to check if any submissions are waiting to
644             * be graded.
645             * 
646             * @return A submission, or null if the queue is empty.
647             */
648            public Submission pollGradeQueue() {
649                    return gradingQueue.poll();
650            }
651    
652            /**
653             * Called by the grading thread to check if any submissions are waiting to
654             * be run. This polls the practice queue, which is for submissions that are
655             * not scored and are only fed the sample input.
656             * 
657             * @return A submission, or null if the queue is empty.
658             */
659            public Submission pollPracticeQueue() {
660                    return practiceQueue.poll();
661            }
662    
663            /**
664             * Registers a team home with the competition manager
665             * 
666             * @param th
667             *            The team home to register
668             * @deprecated
669             */
670            public void registerTeamHome(TeamHome th) {
671                    Team t = th.getController().getTeam();
672                    if (teamHomes.containsKey(t)) {
673                            log.debug("Team already registered, overwriting");
674                            TeamHome old = teamHomes.get(t);
675                            old.cleanup();
676                    }
677                    teamHomes.put(t, th);
678            }
679    
680            /**
681             * Add <b>ALL</b> submissions for this competition to the grading queue.<br />
682             * This method is used mainly for testing and has little real world use.
683             */
684            public void reGradeAllSubs() {
685                    checkGThreads();
686                    List<TeamSubmission> subs = new LinkedList<TeamSubmission>();
687                    List<Problem> probs = currentComp.getProblems();
688                    for (Problem p : probs) {
689                            subs.addAll(p.getSubmissions());
690                    }
691                    for (TeamSubmission s : subs) {
692                            if (s.getStatus() != null
693                                    && s.getStatus().equals(getTestStatus(s.getObjectContext())))
694                                    addSubmissionToPracticeQueue(s);
695                            else
696                                    addSubmissionToGradeQueue(s);
697                            s.setStatus(null);
698                            s.setPoints(0);
699                    }
700                    currentComp.getObjectContext().commitChanges();
701    
702            }
703    
704            /**
705             * Sets the <code>auto_compile_error</code> flag
706             * 
707             * @param on
708             *            the new value
709             */
710            public void setAutoCompileError(boolean on) {
711                    getCompetition().setProp("auto_compile_error", on);
712            }
713    
714            /**
715             * Sets <code>auto_correct</code> flag for this competition.
716             * 
717             * @param autoOn
718             *            true if the system should mark submissions correct when they
719             *            match the expected output exactly.
720             */
721            public void setAutoCorrect(boolean autoOn) {
722                    getCompetition().setProp("auto_correct", autoOn);
723            }
724    
725            /**
726             * Sets the value of the <code>auto_runtime_error</code> flag
727             * 
728             * @param on
729             *            the new value
730             */
731            public void setAutoRuntimeError(boolean on) {
732                    getCompetition().setProp("auto_runtime_error", on);
733            }
734    
735            /**
736             * @param compile_error_status
737             *            the compile_error_status to set
738             */
739            public void setCompile_error_status(int compile_error_status) {
740                    getCompetition().setProp("compile_error_status", compile_error_status);
741            }
742    
743            /**
744             * @see #compIsRunning
745             * @param compIsRunning
746             *            The compIsRunning to set
747             */
748            public void setCompIsRunning(boolean compIsRunning) {
749                    this.compIsRunning = compIsRunning;
750            }
751    
752            /**
753             * @param correct_status
754             *            the correct_status to set
755             */
756            public void setCorrect_status(int correct_status) {
757                    getCompetition().setProp("correct_status", correct_status);
758            }
759    
760            /**
761             * Set this manager's competition
762             * 
763             * @param currentComp
764             *            The new competition
765             */
766            public void setCurrentComp(Competition currentComp) {
767                    this.currentComp = currentComp;
768            }
769    
770            /**
771             * Set the max time for an execution.
772             * 
773             * @param tout
774             */
775            public void setExeTimeout(int tout) {
776                    getCompetition().setProp("exe_timeout", tout);
777            }
778    
779            /**
780             * Sets the number of grading threads for this competition.
781             * 
782             * @param num
783             *            if num is greater than the current number of threads, new
784             *            threads are started. If num is less than the current number of
785             *            threads, the excess graders are stopped.
786             */
787            public void setNumGraders(int num) {
788                    if (num <= 0)
789                            throw new IllegalArgumentException("num must be greater than zero!");
790                    checkGThreads(false);
791                    if (num == numGradingThreads) {
792                            return;
793                    } else if (num > numGradingThreads) {
794                            while (num > numGradingThreads) {
795                                    GradingThread gt = new GradingThread(this);
796                                    gt.start();
797                                    gThreads.add(gt);
798                                    numGradingThreads++;
799                            }
800                    } else if (num < numGradingThreads) {
801                            while (num < numGradingThreads) {
802                                    gThreads.get(0).stopGrading();
803                                    gThreads.remove(0);
804                                    numGradingThreads--;
805                            }
806                    }
807                    numGradingThreads = num;
808                    if (gThreads.size() != numGradingThreads)
809                            checkGThreads(true);
810                    getCompetition().setProp("num_graders", num);
811    
812            }
813    
814            /**
815             * @param presentation_error_status
816             *            the presentation_error_status to set
817             */
818            public void setPresentation_error_status(int presentation_error_status) {
819                    getCompetition().setProp("presentation_error_status",
820                            presentation_error_status);
821            }
822    
823            /**
824             * @param runtime_error_status
825             *            the runtime_error_status to set
826             */
827            public void setRuntime_error_status(int runtime_error_status) {
828                    getCompetition().setProp("runtime_error_status", runtime_error_status);
829            }
830    
831            /**
832             * Set the scoring mode to be used by the competition
833             * 
834             * @param mode
835             *            The new scoring mode
836             */
837            public void setScoringMode(ScoringMode mode) {
838                    getCompetition().setProp("scoring_mode", mode);
839            }
840    
841            /**
842             * @param test_status
843             *            the test_status to set
844             */
845            public void setTest_status(int test_status) {
846                    getCompetition().setProp("test_status", test_status);
847            }
848    
849            /**
850             * @param timelimit_error_status
851             *            the timelimit_error_status to set
852             */
853            public void setTimelimit_error_status(int timelimit_error_status) {
854                    getCompetition().setProp("timelimit_error_status",
855                            timelimit_error_status);
856            }
857    
858            /**
859             * Set the <code>use_security</code> property.
860             * 
861             * @param on
862             * @see #getUseSecurity()
863             */
864            public void setUseSecurity(boolean on) {
865                    getCompetition().setProp("use_security", on);
866            }
867    
868            /**
869             * @param wrong_output_status
870             *            the wrong_output_status to set
871             */
872            public void setWrong_output_status(int wrong_output_status) {
873                    getCompetition().setProp("wrong_output_status", wrong_output_status);
874            }
875    
876            public void unRegisterTeamHome(TeamHome th) {
877                    Team t = th.getController().getTeam();
878                    teamHomes.remove(t);
879            }
880    
881            private void checkGThreads(boolean fix) {
882                    Iterator<GradingThread> gti = gThreads.iterator();
883                    while (gti.hasNext())
884                            if (!gti.next().isAlive())
885                                    gti.remove();
886                    if (fix && numGradingThreads != gThreads.size()) {
887                            int expNum = numGradingThreads;
888                            numGradingThreads = gThreads.size();
889                            setNumGraders(expNum);
890                    }
891                    numGradingThreads = gThreads.size();
892            }
893    
894            private void initGThreads() {
895                    GradingThread gt;
896                    for (int i = 0; i < numGradingThreads; i++) {
897                            gt = new GradingThread(this);
898                            gt.start();
899                            gThreads.add(gt);
900                    }
901            }
902    
903            /**
904             * We need to make sure that we stop the grading thread for this competition
905             * when we clean up this object, also deregisters the listeners.
906             */
907            @Override
908            protected void finalize() throws Throwable {
909                    SubmissionListener.deRegisterCallback(this);
910                    for (GradingThread gt : gThreads)
911                            gt.stopGrading();
912            }
913    }