/**
* <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.manager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.DoubleAdder;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder;
import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder.AbstractEntry;
import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder.NumericalEntry;
import org.olat.ims.qti21.model.xml.interactions.FIBAssessmentItemBuilder.TextEntry;
import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
import uk.ac.ed.ph.jqtiplus.node.item.CorrectResponse;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.ChoiceInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction;
import uk.ac.ed.ph.jqtiplus.node.item.interaction.TextEntryInteraction;
import uk.ac.ed.ph.jqtiplus.node.item.response.declaration.ResponseDeclaration;
import uk.ac.ed.ph.jqtiplus.node.shared.FieldValue;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.value.BaseType;
import uk.ac.ed.ph.jqtiplus.value.Cardinality;
import uk.ac.ed.ph.jqtiplus.value.DirectedPairValue;
import uk.ac.ed.ph.jqtiplus.value.IdentifierValue;
import uk.ac.ed.ph.jqtiplus.value.IntegerValue;
import uk.ac.ed.ph.jqtiplus.value.MultipleValue;
import uk.ac.ed.ph.jqtiplus.value.OrderedValue;
import uk.ac.ed.ph.jqtiplus.value.PairValue;
import uk.ac.ed.ph.jqtiplus.value.PointValue;
import uk.ac.ed.ph.jqtiplus.value.SingleValue;
import uk.ac.ed.ph.jqtiplus.value.Value;
/**
* This is a set of methods to analyse the stringuified responses
* save in the database.
*
* Initial date: 26.04.2016<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class CorrectResponsesUtil {
private static final OLog log = Tracing.createLoggerFor(CorrectResponsesUtil.class);
/**
* Remove the leading and trailing [ ] if exists.
* @param stringuifiedResponses
* @return
*/
public static final String stripResponse(String stringuifiedResponses) {
if(stringuifiedResponses != null) {
if(stringuifiedResponses.startsWith("[")) {
stringuifiedResponses = stringuifiedResponses.substring(1, stringuifiedResponses.length());
}
if(stringuifiedResponses.endsWith("]")) {
stringuifiedResponses = stringuifiedResponses.substring(0, stringuifiedResponses.length() - 1);
}
}
return stringuifiedResponses;
}
/**
* Parse response in the form [34 45][test] and return "23 45", test.
* @return
*/
public static final List<String> parseResponses(String stringuifiedResponse) {
List<String> responses = new ArrayList<>();
if(StringHelper.containsNonWhitespace(stringuifiedResponse)) {
StringBuilder sb = new StringBuilder();
int numOfChars = stringuifiedResponse.length();
for(int i=0;i<numOfChars; i++) {
char ch = stringuifiedResponse.charAt(i);
if(ch == '[') {
sb = new StringBuilder();
} else if(ch == ']') {
responses.add(sb.toString());
} else {
sb.append(ch);
}
}
}
return responses;
}
/**
* The method ignore the wrong formatted coordinates
* @param responses
* @return
*/
public static final List<PointValue> parseResponses(List<String> responses) {
List<PointValue> points = new ArrayList<>();
for(String response:responses) {
if(StringHelper.containsNonWhitespace(response)) {
try {
PointValue pointValue = PointValue.parseString(response);
points.add(pointValue);
} catch (Exception e) {
log.error("", e);
}
}
}
return points;
}
public static final int[] convertCoordinates(final List<Integer> coords) {
final int[] result = new int[coords.size()];
for (int i = 0; i < result.length; i++) {
result[i] = coords.get(i).intValue();
}
return result;
}
/**
* Calculate the list of correct responses found in the response of the assessed user.
*
* @param item
* @param choiceInteraction
* @param stringuifiedResponse
* @return
*/
public static final List<Identifier> getCorrectAnsweredResponses(AssessmentItem assessmentItem, ChoiceInteraction choiceInteraction, String stringuifiedResponse) {
List<Identifier> correctAnsweredResponses;
if(StringHelper.containsNonWhitespace(stringuifiedResponse)) {
List<Identifier> correctResponses = getCorrectIdentifierResponses(assessmentItem, choiceInteraction);
correctAnsweredResponses = new ArrayList<>(correctResponses.size());
for(Identifier correctResponse:correctResponses) {
String correctIdentifier = correctResponse.toString();
boolean correct = stringuifiedResponse.contains("[" + correctIdentifier + "]");
if(correct) {
correctAnsweredResponses.add(correctResponse);
}
}
} else {
correctAnsweredResponses = Collections.emptyList();
}
return correctAnsweredResponses;
}
/**
* Search the correct responses defined in response declaration of type Identifier.
*
* @param assessmentItem
* @param interaction
* @return
*/
public static final List<Identifier> getCorrectIdentifierResponses(AssessmentItem assessmentItem, Interaction interaction) {
return getCorrectIdentifierResponses(assessmentItem, interaction.getResponseIdentifier());
}
public static final List<Identifier> getCorrectIdentifierResponses(AssessmentItem assessmentItem, Identifier responseIdentifier) {
List<Identifier> correctAnswers = new ArrayList<>(5);
ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(responseIdentifier);
if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) {
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
if(correctResponse.getCardinality().isOneOf(Cardinality.SINGLE)) {
List<FieldValue> values = correctResponse.getFieldValues();
Value value = FieldValue.computeValue(Cardinality.SINGLE, values);
if(value instanceof IdentifierValue) {
IdentifierValue identifierValue = (IdentifierValue)value;
Identifier correctAnswer = identifierValue.identifierValue();
correctAnswers.add(correctAnswer);
}
} else if(correctResponse.getCardinality().isOneOf(Cardinality.MULTIPLE)) {
Value value = FieldValue.computeValue(Cardinality.MULTIPLE, correctResponse.getFieldValues());
if(value instanceof MultipleValue) {
MultipleValue multiValue = (MultipleValue)value;
for(SingleValue sValue:multiValue.getAll()) {
if(sValue instanceof IdentifierValue) {
IdentifierValue identifierValue = (IdentifierValue)sValue;
Identifier correctAnswer = identifierValue.identifierValue();
correctAnswers.add(correctAnswer);
}
}
}
}
}
return correctAnswers;
}
public static final List<Integer> getCorrectIntegerResponses(AssessmentItem assessmentItem, Interaction interaction) {
List<Integer> correctAnswers = new ArrayList<>(5);
ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier());
if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) {
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
if(correctResponse.getCardinality().isOneOf(Cardinality.SINGLE)) {
List<FieldValue> values = correctResponse.getFieldValues();
Value value = FieldValue.computeValue(Cardinality.SINGLE, values);
if(value instanceof IntegerValue) {
IntegerValue identifierValue = (IntegerValue)value;
correctAnswers.add(identifierValue.intValue());
}
} else if(correctResponse.getCardinality().isOneOf(Cardinality.MULTIPLE)) {
Value value = FieldValue.computeValue(Cardinality.MULTIPLE, correctResponse.getFieldValues());
if(value instanceof MultipleValue) {
MultipleValue multiValue = (MultipleValue)value;
for(SingleValue sValue:multiValue.getAll()) {
if(sValue instanceof IntegerValue) {
IntegerValue identifierValue = (IntegerValue)value;
correctAnswers.add(identifierValue.intValue());
}
}
}
}
}
return correctAnswers;
}
public static final List<Identifier> getCorrectOrderedIdentifierResponses(AssessmentItem assessmentItem, Interaction interaction) {
List<Identifier> correctAnswers = new ArrayList<>(5);
ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier());
if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) {
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
if(correctResponse.getCardinality().isOneOf(Cardinality.ORDERED)) {
List<FieldValue> values = correctResponse.getFieldValues();
Value value = FieldValue.computeValue(Cardinality.ORDERED, values);
if(value instanceof OrderedValue) {
OrderedValue multiValue = (OrderedValue)value;
multiValue.forEach(oValue -> {
if(oValue instanceof IdentifierValue) {
IdentifierValue identifierValue = (IdentifierValue)oValue;
Identifier correctAnswer = identifierValue.identifierValue();
correctAnswers.add(correctAnswer);
}
});
}
}
}
return correctAnswers;
}
public static final List<String> getCorrectMultiplePairResponses(AssessmentItem assessmentItem, Interaction interaction, boolean withDelimiter) {
final List<String> correctAnswers = new ArrayList<>(5);
ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier());
if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) {
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
if(correctResponse.getCardinality().isOneOf(Cardinality.MULTIPLE)) {
List<FieldValue> values = correctResponse.getFieldValues();
Value value = FieldValue.computeValue(Cardinality.MULTIPLE, values);
if(value instanceof MultipleValue) {
MultipleValue multiValue = (MultipleValue)value;
multiValue.forEach(oValue -> {
if(oValue instanceof PairValue) {
PairValue pairValue = (PairValue)oValue;
String source = pairValue.sourceValue().toString();
String destination = pairValue.destValue().toString();
if(withDelimiter) {
correctAnswers.add("[" + source + " " + destination + "]");
} else {
correctAnswers.add(source + " " + destination);
}
}
});
}
}
}
return correctAnswers;
}
/**
* The list of correct associations
* @param assessmentItem
* @param interaction
* @return A list of string with [ and ] before and after!
*/
public static final Set<String> getCorrectDirectPairResponses(AssessmentItem assessmentItem, Interaction interaction, boolean withDelimiter) {
ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier());
Set<String> correctAnswers = new HashSet<>();
//readable responses
if(responseDeclaration != null && responseDeclaration.getCorrectResponse() != null) {
CorrectResponse correctResponse = responseDeclaration.getCorrectResponse();
if(correctResponse.getCardinality().isOneOf(Cardinality.MULTIPLE)) {
List<FieldValue> values = correctResponse.getFieldValues();
Value value = FieldValue.computeValue(Cardinality.MULTIPLE, values);
if(value instanceof MultipleValue) {
MultipleValue multiValue = (MultipleValue)value;
multiValue.forEach(oValue -> {
if(oValue instanceof DirectedPairValue) {
DirectedPairValue pairValue = (DirectedPairValue)oValue;
String source = pairValue.sourceValue().toString();
String destination = pairValue.destValue().toString();
if(withDelimiter) {
correctAnswers.add("[" + source + " " + destination + "]");
} else {
correctAnswers.add(source + " " + destination);
}
}
});
}
}
}
return correctAnswers;
}
public static final AbstractEntry getCorrectTextResponses(AssessmentItem assessmentItem, TextEntryInteraction interaction) {
ResponseDeclaration responseDeclaration = assessmentItem.getResponseDeclaration(interaction.getResponseIdentifier());
if(responseDeclaration.hasBaseType(BaseType.STRING) && responseDeclaration.hasCardinality(Cardinality.SINGLE)) {
TextEntry textEntry = new TextEntry(interaction);
FIBAssessmentItemBuilder.extractTextEntrySettingsFromResponseDeclaration(textEntry, responseDeclaration, new AtomicInteger(), new DoubleAdder());
return textEntry;
} else if(responseDeclaration.hasBaseType(BaseType.FLOAT) && responseDeclaration.hasCardinality(Cardinality.SINGLE)) {
NumericalEntry numericalEntry = new NumericalEntry(interaction);
FIBAssessmentItemBuilder.extractNumericalEntrySettings(assessmentItem, numericalEntry, responseDeclaration, new AtomicInteger(), new DoubleAdder());
return numericalEntry;
}
return null;
}
}