/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <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 the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <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>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.ims.qti21.model.xml;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.olat.ims.qti21.QTI21Constants;
import org.olat.ims.qti21.model.IdentifierGenerator;
import uk.ac.ed.ph.jqtiplus.node.expression.Expression;
import uk.ac.ed.ph.jqtiplus.node.expression.general.BaseValue;
import uk.ac.ed.ph.jqtiplus.node.expression.operator.Sum;
import uk.ac.ed.ph.jqtiplus.node.expression.outcome.TestVariables;
import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration;
import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest;
import uk.ac.ed.ph.jqtiplus.node.test.TestFeedback;
import uk.ac.ed.ph.jqtiplus.node.test.TimeLimits;
import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeCondition;
import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeConditionChild;
import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeIf;
import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeProcessing;
import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.OutcomeRule;
import uk.ac.ed.ph.jqtiplus.node.test.outcome.processing.SetOutcomeValue;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.value.FloatValue;
/**
* This builder only build OpenOLAT QTI tests.
*
*
* Initial date: 11.12.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class AssessmentTestBuilder {
private final AssessmentTest assessmentTest;
private final AssessmentHtmlBuilder htmlBuilder;
private final boolean editable;
private Double cutValue;
private Double maxScore;
private Long maximumTimeLimits;
private OutcomeRule testScoreRule;
private OutcomeCondition cutValueRule;
private TestFeedbackBuilder passedFeedback;
private TestFeedbackBuilder failedFeedback;
private List<TestFeedbackBuilder> additionalFeedbacks = new ArrayList<>();
public AssessmentTestBuilder(AssessmentTest assessmentTest) {
this.assessmentTest = assessmentTest;
htmlBuilder = new AssessmentHtmlBuilder();
editable = "OpenOLAT".equals(assessmentTest.getToolName());
extract();
}
private void extract() {
extractMaxScore();
extractRules();
extractFeedbacks();
extractTimeLimits();
}
private void extractMaxScore() {
maxScore = QtiNodesExtractor.extractMaxScore(assessmentTest);
}
private void extractRules() {
if(assessmentTest.getOutcomeProcessing() != null) {
List<OutcomeRule> outcomeRules = assessmentTest.getOutcomeProcessing().getOutcomeRules();
for(OutcomeRule outcomeRule:outcomeRules) {
// export test score
if(outcomeRule instanceof SetOutcomeValue) {
SetOutcomeValue setOutcomeValue = (SetOutcomeValue)outcomeRule;
if(QTI21Constants.SCORE_IDENTIFIER.equals(setOutcomeValue.getIdentifier())) {
testScoreRule = outcomeRule;
}
}
// pass rule
if(outcomeRule instanceof OutcomeCondition) {
OutcomeCondition outcomeCondition = (OutcomeCondition)outcomeRule;
boolean findIf = findSetOutcomeValue(outcomeCondition.getOutcomeIf(), QTI21Constants.PASS_IDENTIFIER);
boolean findElse = findSetOutcomeValue(outcomeCondition.getOutcomeElse(), QTI21Constants.PASS_IDENTIFIER);
if(findIf && findElse) {
cutValue = extractCutValue(outcomeCondition.getOutcomeIf());
cutValueRule = outcomeCondition;
}
}
}
}
}
public static Double extractCutValue(OutcomeIf outcomeIf) {
if(outcomeIf != null && outcomeIf.getExpressions().size() > 0) {
Expression gte = outcomeIf.getExpressions().get(0);
if(gte.getExpressions().size() > 1) {
Expression baseValue = gte.getExpressions().get(1);
if(baseValue instanceof BaseValue) {
BaseValue value = (BaseValue)baseValue;
if(value.getSingleValue() instanceof FloatValue) {
return ((FloatValue)value.getSingleValue()).doubleValue();
}
}
}
}
return null;
}
public static boolean findSetOutcomeValue(OutcomeConditionChild outcomeConditionChild, Identifier identifier) {
if(outcomeConditionChild == null
|| outcomeConditionChild.getOutcomeRules() == null
|| outcomeConditionChild.getOutcomeRules().isEmpty()) return false;
List<OutcomeRule> outcomeRules = outcomeConditionChild.getOutcomeRules();
for(OutcomeRule outcomeRule:outcomeRules) {
SetOutcomeValue setOutcomeValue = (SetOutcomeValue)outcomeRule;
if(identifier.equals(setOutcomeValue.getIdentifier())) {
return true;
}
}
return false;
}
private void extractFeedbacks() {
List<TestFeedback> feedbacks = assessmentTest.getTestFeedbacks();
for(TestFeedback feedback:feedbacks) {
TestFeedbackBuilder feedbackBuilder = new TestFeedbackBuilder(assessmentTest, feedback);
if(feedbackBuilder.isPassedRule()) {
passedFeedback = feedbackBuilder;
} else if(feedbackBuilder.isFailedRule()) {
failedFeedback = feedbackBuilder;
} else {
additionalFeedbacks.add(feedbackBuilder);
}
}
}
private void extractTimeLimits() {
TimeLimits timeLimits = assessmentTest.getTimeLimits();
if(timeLimits != null && timeLimits.getMaximum() != null) {
maximumTimeLimits = timeLimits.getMaximum().longValue();
}
}
public boolean isEditable() {
return editable;
}
public AssessmentTest getAssessmentTest() {
return assessmentTest;
}
public Double getCutValue() {
return cutValue;
}
public void setCutValue(Double cutValue) {
this.cutValue = cutValue;
}
public Double getMaxScore() {
return maxScore;
}
public void setMaxScore(Double maxScore) {
this.maxScore = maxScore;
}
/**
* @return The maximum time for the test in seconds.
*/
public Long getMaximumTimeLimits() {
return maximumTimeLimits;
}
/**
* The maximum time to solve the test in seconds.
* @param maximumTimeLimits A positove value in seconds or null
*/
public void setMaximumTimeLimits(Long maximumTimeLimits) {
this.maximumTimeLimits = maximumTimeLimits;
}
public TestFeedbackBuilder getPassedFeedback() {
return passedFeedback;
}
public TestFeedbackBuilder createPassedFeedback() {
passedFeedback = new TestFeedbackBuilder(assessmentTest, null);
return passedFeedback;
}
public TestFeedbackBuilder getFailedFeedback() {
return failedFeedback;
}
public TestFeedbackBuilder createFailedFeedback() {
failedFeedback = new TestFeedbackBuilder(assessmentTest, null);
return failedFeedback;
}
public AssessmentTest build() {
if(!editable) {
return assessmentTest;
}
if(assessmentTest.getOutcomeProcessing() == null) {
assessmentTest.setOutcomeProcessing(new OutcomeProcessing(assessmentTest));
}
if(maximumTimeLimits != null) {
TimeLimits timeLimits = new TimeLimits(assessmentTest);
timeLimits.setMaximum(maximumTimeLimits.doubleValue());
assessmentTest.setTimeLimits(timeLimits);
} else {
assessmentTest.setTimeLimits(null);
}
buildScore();
buildTestScore();
buildCutValue();
buildFeedback();
//clean up
if(assessmentTest.getOutcomeProcessing().getOutcomeRules().isEmpty()) {
assessmentTest.setOutcomeProcessing(null);
}
return assessmentTest;
}
private void buildScore() {
OutcomeDeclaration scoreDeclaration = assessmentTest.getOutcomeDeclaration(QTI21Constants.SCORE_IDENTIFIER);
if(scoreDeclaration == null) {
scoreDeclaration = AssessmentTestFactory.createOutcomeDeclaration(assessmentTest, QTI21Constants.SCORE_IDENTIFIER, 0.0d);
assessmentTest.getOutcomeDeclarations().add(0, scoreDeclaration);
}
if(maxScore != null) {
OutcomeDeclaration maxScoreDeclaration = assessmentTest.getOutcomeDeclaration(QTI21Constants.MAXSCORE_IDENTIFIER);
if(maxScoreDeclaration == null) {
maxScoreDeclaration = AssessmentTestFactory.createOutcomeDeclaration(assessmentTest, QTI21Constants.MAXSCORE_IDENTIFIER, maxScore);
assessmentTest.getOutcomeDeclarations().add(0, maxScoreDeclaration);
} else {//update value
AssessmentTestFactory.updateDefaultValue(maxScoreDeclaration, maxScore);
}
} else {
OutcomeDeclaration maxScoreDeclaration = assessmentTest.getOutcomeDeclaration(QTI21Constants.MAXSCORE_IDENTIFIER);
if(maxScoreDeclaration != null) {
assessmentTest.getOutcomeDeclarations().remove(maxScoreDeclaration);
}
}
}
/* Overall score of this test
<setOutcomeValue identifier="SCORE">
<sum>
<testVariables variableIdentifier="SCORE" />
</sum>
</setOutcomeValue>
*/
private void buildTestScore() {
if(testScoreRule == null) {
SetOutcomeValue scoreRule = new SetOutcomeValue(assessmentTest);
scoreRule.setIdentifier(QTI21Constants.SCORE_IDENTIFIER);
Sum sum = new Sum(scoreRule);
scoreRule.getExpressions().add(sum);
TestVariables testVariables = new TestVariables(sum);
sum.getExpressions().add(testVariables);
testVariables.setVariableIdentifier(QTI21Constants.SCORE_IDENTIFIER);
assessmentTest.getOutcomeProcessing().getOutcomeRules().add(0, scoreRule);
testScoreRule = scoreRule;
}
}
/* Passed
<outcomeCondition>
<outcomeIf>
<gte>
<sum>
<testVariables variableIdentifier="SCORE" />
</sum>
<baseValue baseType="float">
1
</baseValue>
</gte>
<setOutcomeValue identifier="PASS">
<baseValue baseType="boolean">
true
</baseValue>
</setOutcomeValue>
</outcomeIf>
<outcomeElse>
<setOutcomeValue identifier="PASS">
<baseValue baseType="boolean">
false
</baseValue>
</setOutcomeValue>
</outcomeElse>
</outcomeCondition>
*/
private void buildCutValue() {
if(cutValue != null) {
OutcomeDeclaration passDeclaration = assessmentTest.getOutcomeDeclaration(QTI21Constants.PASS_IDENTIFIER);
if(passDeclaration == null) {
passDeclaration = AssessmentTestFactory.createOutcomeDeclaration(assessmentTest, QTI21Constants.PASS_IDENTIFIER, false);
assessmentTest.getOutcomeDeclarations().add(passDeclaration);
}
boolean updated = false;
if(cutValueRule != null && cutValueRule.getOutcomeIf().getExpressions().size() > 0) {
Expression gte = cutValueRule.getOutcomeIf().getExpressions().get(0);
if(gte.getExpressions().size() > 1) {
Expression baseValue = gte.getExpressions().get(1);
if(baseValue instanceof BaseValue) {
BaseValue value = (BaseValue)baseValue;
value.setSingleValue(new FloatValue(cutValue.doubleValue()));
updated = true;
}
}
}
if(!updated) {
assessmentTest.getOutcomeProcessing().getOutcomeRules().remove(cutValueRule);
cutValueRule = AssessmentTestFactory.createCutValueRule(assessmentTest, cutValue);
assessmentTest.getOutcomeProcessing().getOutcomeRules().add(cutValueRule);
}
} else if(cutValueRule != null) {
assessmentTest.getOutcomeDeclarations().remove(cutValueRule);
}
}
private void buildFeedback() {
//remove outcome rules
List<OutcomeRule> outcomeRules = assessmentTest.getOutcomeProcessing().getOutcomeRules();
for(Iterator<OutcomeRule> outcomeRuleIt=outcomeRules.iterator(); outcomeRuleIt.hasNext(); ) {
OutcomeRule outcomeRule = outcomeRuleIt.next();
if(outcomeRule instanceof OutcomeCondition) {
OutcomeCondition outcomeCondition = (OutcomeCondition)outcomeRule;
if(outcomeCondition.getOutcomeIf() != null && outcomeCondition.getOutcomeIf().getOutcomeRules().size() == 1) {
OutcomeRule outcomeValue = outcomeCondition.getOutcomeIf().getOutcomeRules().get(0);
if(outcomeValue instanceof SetOutcomeValue) {
SetOutcomeValue setOutcomeValue = (SetOutcomeValue)outcomeValue;
if(QTI21Constants.FEEDBACKMODAL_IDENTIFIER.equals(setOutcomeValue.getIdentifier())) {
outcomeRuleIt.remove();
}
}
}
}
}
//set the feedbackmodal outcome declaration if needed
if(passedFeedback != null || failedFeedback != null) {
OutcomeDeclaration outcomeDeclaration = assessmentTest.getOutcomeDeclaration(QTI21Constants.FEEDBACKMODAL_IDENTIFIER);
if(outcomeDeclaration == null) {
OutcomeDeclaration feedbackModalOutcomeDeclaration = AssessmentTestFactory
.createTestFeedbackModalOutcomeDeclaration(assessmentTest);
assessmentTest.getOutcomeDeclarations().add(feedbackModalOutcomeDeclaration);
}
}
if(passedFeedback != null) {
buildFeedback(passedFeedback, true);
}
if(failedFeedback != null) {
buildFeedback(failedFeedback, false);
}
}
private void buildFeedback(TestFeedbackBuilder feedbackBuilder, boolean passed) {
if(htmlBuilder.containsSomething(feedbackBuilder.getText())) {
TestFeedback testFeedback;
if(feedbackBuilder.getTestFeedback() == null) {
testFeedback = AssessmentTestFactory
.createTestFeedbackModal(assessmentTest, IdentifierGenerator.newAsIdentifier("fm") , feedbackBuilder.getTitle(), feedbackBuilder.getText());
assessmentTest.getTestFeedbacks().add(testFeedback);
} else {
testFeedback = feedbackBuilder.getTestFeedback();
testFeedback.setTitle(feedbackBuilder.getTitle());
htmlBuilder.appendHtml(testFeedback, feedbackBuilder.getText());
}
OutcomeCondition outcomeCondition = AssessmentTestFactory
.createTestFeedbackModalCondition(assessmentTest, passed, testFeedback.getOutcomeValue());
assessmentTest.getOutcomeProcessing().getOutcomeRules().add(outcomeCondition);
} else if(feedbackBuilder.getTestFeedback() != null) {
assessmentTest.getTestFeedbacks().remove(feedbackBuilder.getTestFeedback());
}
}
}