package tudelft.healthpsychology.traumaontologies.answerstate; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import tudelft.healthpsychology.traumaontologies.questiontypes.Bool; import tudelft.healthpsychology.traumaontologies.questiontypes.TypedQuestion; import tudelft.utilities.tree.Tree; /** * {@link #node} points to a type of object of which there may be multiple * relevant instances , eg "Persoon". This keeps track of asking the user all * details about all instances. * * So this contains the state of a set of answers for a given OntologyNode, * handling breath first: *
    *
  1. The top level node eg #Persoon contains a main question (with the name as * indicated in the constructor). * *
  2. We repeat asking for this main question until user says he has no more * relevant items of this type (so no more relevant person) * *
  3. for each item, we determine the best fitting ontology class and ask the * questions for this class. *
* */ public class AnswersBreathFirstState implements AnswerState { private final String nodelabel; // root node that this answer is about private final List answerstates; // the main properties of the nodes. // We keep asking for more of these as long as this is not null. private final Property mainProperty; private final boolean askForMore; private final Bool morequestion; /** * Construct new state with a list of OntologyAnswerState, the last being * possibly nonfinal If the last is final and askedForMore=true * * @param node the node label that this answer is about. * @param answerstates the list of answer states about node/mainprop. * @param mainprop the main question, eg the property related to "Naam". * This is the property to be asked first. node must * have a property with this question. If not null, the * user wants to add a mainprop. If mainprop=null, it * means the user does not want to add more items. This * can be null only if answerstates contains at least 1 * item. * @param askMore true if we need to ask {@link #MOREANSWER}. Can only * be true if mainprop is not null. If false, we look at * mainprop to see if we need to add more. * @param morequestion the question to ask for more, eg "zijn er nog meer * relevante personen?". */ @JsonCreator public AnswersBreathFirstState(@JsonProperty("nodelabel") String node, @JsonProperty("answerstates") List answerstates, @JsonProperty("mainProperty") Property mainprop, @JsonProperty("askForMore") boolean askMore, @JsonProperty("morequestion") Bool morequestion) { if (answerstates.isEmpty() && mainprop == null) { throw new IllegalArgumentException( "mainprop must be not null if answerstates is empty"); } if (askMore && mainprop == null) { throw new IllegalArgumentException( "If askForMore then mainprop must be not null"); } this.nodelabel = node; this.answerstates = answerstates; this.mainProperty = mainprop; this.askForMore = askMore; this.morequestion = morequestion; } /** * Convenience constructor : new state with one open OntologyAnswerState * * @param node the node that this answer is about. * @param mainproperty the main question property, eg "Naam". This is the * question of the property to be asked first. The node * must have a property with this question. * @param havemore the question to as if there are more items like this. */ public AnswersBreathFirstState(OntologyNode node, String mainproperty, String havemore) { this(node.getLabel(), new LinkedList<>(), getMainProp(node, mainproperty), false, new Bool(havemore, "internal")); } @Override public TypedQuestion getOptions( Tree, OntologyNode> tree) { if (askForMore) { return morequestion; } if (mainProperty != null) { return mainProperty.getQuestionType(); } // now handle each one depth-first. for (OntoPropAnswerState state : answerstates) { TypedQuestion opts = state.getOptions(tree); if (opts != null) return opts; } return null; } @Override public AnswersBreathFirstState with(String answer, Tree, OntologyNode> tree) { TypedQuestion opts = getOptions(tree); if (opts == null) return this; if (!opts.fits(answer)) { throw new IllegalArgumentException( "Answer " + answer + " does not fit " + opts); } OntologyNode node = tree.get(nodelabel); if (askForMore) { /** * question was MOREANSWER. Just handle the yes/no answer. if answer * is no, set mainProperty to null */ return new AnswersBreathFirstState(nodelabel, answerstates, Bool.matchesYes(answer) ? mainProperty : null, false, morequestion); } // askForMore may be true or false if we get here. Check mainproperty if (mainProperty != null) { /* * User answered main question. Add new OntoPropAnswerState with the * first answer already there . NOTICE we create node here with * WRONG NODE (Typically "Persoon" ipv eg "Collega" // as we do not * know the correct node type at this point */ LinkedList newAnswerStates = new LinkedList<>( answerstates); Map answer1 = new HashMap<>(); answer1.put(mainProperty, answer); PropertiesAnswerState firstAnswer = new PropertiesAnswerState( nodelabel, answer1); newAnswerStates.add(new OntoPropAnswerState( new OntologyAnswerState(node, answer), firstAnswer)); return new AnswersBreathFirstState(nodelabel, newAnswerStates, mainProperty, true, morequestion); } /* * If we get here, mainProperty=null. We're done collecting the main * properties. Continue depth first now on collected answerstates */ int activeanswer = 0; while (answerstates.get(activeanswer).getOptions(tree) == null) activeanswer++; LinkedList newAnswerStates = new LinkedList<>( answerstates); newAnswerStates.set(activeanswer, answerstates.get(activeanswer).with(answer, tree)); return new AnswersBreathFirstState(nodelabel, newAnswerStates, null, false, morequestion); } /** * * @param node * @param mainquestion * @return the {@link Property} of the node of which the ID equals to the * given mainquestion. */ private static Property getMainProp(OntologyNode node, String mainquestion) { Optional mainprop = node.getAttribute().stream().filter( prop -> mainquestion.equals(prop.getQuestionType().getId())) .findFirst(); if (!mainprop.isPresent()) { throw new IllegalArgumentException("Node " + node + " must have a property/attribute with the main question '" + mainquestion + "'"); } return mainprop.get(); } }