/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.course.run.scoring;
import java.util.HashMap;
import java.util.Map;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.util.Util;
import org.olat.core.util.nodes.INode;
import org.olat.core.util.tree.TreeVisitor;
import org.olat.core.util.tree.Visitor;
import org.olat.course.nodes.AssessableCourseNode;
import org.olat.course.nodes.CourseNode;
import org.olat.course.run.userview.UserCourseEnvironment;
/**
* Description:<BR/>
* The score accounting contains all score evaluations for a user
* <P/>
* Initial Date: Oct 12, 2004
*
* @author Felix Jost
*/
public class ScoreAccounting implements Visitor {
private UserCourseEnvironment userCourseEnvironment;
private boolean error;
private boolean childNotFoundError;
private boolean childNotOfAssessableTypeError;
private CourseNode evaluatingCourseNode;
private String wrongChildID;
private Map cachedScoreEvals = new HashMap();
private int recursionCnt;
/**
* Constructor of the user score accounting object
* @param userCourseEnvironment
*/
public ScoreAccounting(UserCourseEnvironment userCourseEnvironment) {
this.userCourseEnvironment = userCourseEnvironment;
}
/**
* Retrieve all the score evaluations for all course nodes
*/
public void evaluateAll() {
cachedScoreEvals.clear();
recursionCnt = 0;
// collect all assessable nodes and eval 'em
CourseNode root = userCourseEnvironment.getCourseEnvironment().getRunStructure().getRootNode();
// breadth first traversal gives an easier order of evaluation for debugging
// however, for live it is absolutely mandatory to use depth first since using breadth first
// the score accoutings local cache hash map will never be used. this can slow down things like
// crazy (course with 10 tests, 300 users and some crazy score and passed calculations will have
// 10 time performance differences)
TreeVisitor tv = new TreeVisitor(this, root, true); // true=depth first
tv.visitAll();
}
/**
* FIXME:fj: cmp this method and evalCourseNode
* Get the score evaluation for a given course node
* @param courseNode
* @return The score evaluation
*/
public ScoreEvaluation getScoreEvaluation(CourseNode courseNode) {
ScoreEvaluation se = null;
if (courseNode instanceof AssessableCourseNode) {
AssessableCourseNode acn = (AssessableCourseNode) courseNode;
se = acn.getUserScoreEvaluation(userCourseEnvironment);
}
return se;
}
/**
* evals the coursenode or simply returns the evaluation from the cache
* @param cn
* @return ScoreEvaluation
*/
public ScoreEvaluation evalCourseNode(AssessableCourseNode cn) {
// make sure we have no circular calculations
recursionCnt++;
if (recursionCnt > 15) throw new OLATRuntimeException("scoreaccounting.stackoverflow",
new String[]{cn.getIdent(), cn.getShortTitle()},
Util.getPackageName(ScoreAccounting.class),
"stack overflow in scoreaccounting, probably circular logic: acn ="
+ cn.toString(), null);
ScoreEvaluation se = (ScoreEvaluation) cachedScoreEvals.get(cn);
if (se == null) { // result of this node has not been calculated yet, do it
se = cn.getUserScoreEvaluation(userCourseEnvironment);
cachedScoreEvals.put(cn, se);
//System.out.println("cn eval: "+cn+" = "+ se);
}
recursionCnt--;
return se;
}
/**
* ----- to called by getScoreFunction only -----
* @param childId
* @return Float
*/
public Float evalScoreOfCourseNode(String childId) {
CourseNode foundNode = findChildByID(childId);
if (foundNode == null) {
error = true;
childNotFoundError = true;
wrongChildID = childId;
return new Float(-9999999.0f);
}
if (!(foundNode instanceof AssessableCourseNode)) {
error = true;
childNotOfAssessableTypeError = true;
wrongChildID = childId;
return new Float(-1111111.0f);
}
AssessableCourseNode acn = (AssessableCourseNode) foundNode;
ScoreEvaluation se = evalCourseNode(acn);
if (se == null) { // the node could not provide any sensible information on scoring. e.g. a STNode with no calculating rules
String msg = "could not evaluate node " + acn.getShortTitle() + " (" + acn.getIdent() + ")" + "; called by node "
+ (evaluatingCourseNode == null ? "n/a" : evaluatingCourseNode.getShortTitle() + " (" + evaluatingCourseNode.getIdent() + ")");
new OLATRuntimeException(ScoreAccounting.class, "scoreaccounting.evaluationerror.score",
new String[]{acn.getIdent(), acn.getShortTitle()},
Util.getPackageName(ScoreAccounting.class),
msg, null);
}
Float score = se.getScore();
if (score == null) { // a child has no score yet
score = new Float(0.0f); // default to 0.0, so that the condition can be evaluated (zero points makes also the most sense for "no results yet", if to be expressed in a number)
}
return score;
}
/**
* ----- to be called by getPassedFunction only -----
* @param childId
* @return Boolean
*/
public Boolean evalPassedOfCourseNode(String childId) {
CourseNode foundNode = findChildByID(childId);
if (foundNode == null) {
error = true;
childNotFoundError = true;
wrongChildID = childId;
return Boolean.FALSE;
}
if (!(foundNode instanceof AssessableCourseNode)) {
error = true;
childNotOfAssessableTypeError = true;
wrongChildID = childId;
return Boolean.FALSE;
}
AssessableCourseNode acn = (AssessableCourseNode) foundNode;
ScoreEvaluation se = evalCourseNode(acn);
if (se == null) { // the node could not provide any sensible information on scoring. e.g. a STNode with no calculating rules
String msg = "could not evaluate node '" + acn.getShortTitle() + "' (" + acn.getClass().getName() + "," + childId + ")";
new OLATRuntimeException(ScoreAccounting.class, "scoreaccounting.evaluationerror.score",
new String[]{acn.getIdent(), acn.getShortTitle()},
Util.getPackageName(ScoreAccounting.class),
msg, null);
}
Boolean passed = se.getPassed();
if (passed == null) { // a child has no "Passed" yet
passed = Boolean.FALSE;
}
return passed;
}
/**
* Change the score information for the given course node
* @param acn
* @param se
*/
public void scoreInfoChanged(AssessableCourseNode acn, ScoreEvaluation se) {
//FIXME:fj:b use cache infos
/*
// either add a new entry if this is the first score that is provided,
// or overwrite the entry in the cache if a scoreeval existed
cachedScoreEvals.put(cn, se);
// go up the ladder and force each parent to recalulate the score
while ((cn = (CourseNode) cn.getParent()) != null) {
ScoreEvaluation sceval = cn.evalScore(userCourseEnvironment);
cachedScoreEvals.put(cn, sceval);
}*/
//System.out.println("scoreInfoChanged - calc anew:\n"+cachedScoreEvals.toString());
evaluateAll();
}
private CourseNode findChildByID(String id) {
CourseNode foundNode = userCourseEnvironment.getCourseEnvironment().getRunStructure().getNode(id);
return foundNode;
}
/**
* used for error msg and debugging. denotes the coursenode which started a calculation.
* when an error occurs, we know which coursenode contains a faulty formula
* @param evaluatingCourseNode
*/
public void setEvaluatingCourseNode(CourseNode evaluatingCourseNode) {
this.evaluatingCourseNode = evaluatingCourseNode;
}
/**
* @see org.olat.core.util.tree.Visitor#visit(org.olat.core.util.nodes.INode)
*/
public void visit(INode node) {
CourseNode cn = (CourseNode) node;
if (cn instanceof AssessableCourseNode) {
AssessableCourseNode acn = (AssessableCourseNode) cn;
evalCourseNode(acn);
// evalCourseNode will cache all infos
}
// else: non assessable nodes are not interesting here
}
/**
* @return true if an error occured
*/
public boolean isError() {
return error;
}
/**
* @return CourseNode
*/
public CourseNode getEvaluatingCourseNode() {
return evaluatingCourseNode;
}
/**
* @return int
*/
public int getRecursionCnt() {
return recursionCnt;
}
/**
* @return String
*/
public String getWrongChildID() {
return wrongChildID;
}
}