source: TraumaOntologies/src/main/java/tudelft/healthpsychology/traumaontologies/answerstate/OntologyAnswerState.java@ 5

Last change on this file since 5 was 5, checked in by Bart Vastenhouw, 5 years ago

Intermediate update

File size: 9.9 KB
Line 
1package tudelft.healthpsychology.traumaontologies.answerstate;
2
3import java.util.Collection;
4import java.util.List;
5import java.util.Optional;
6import java.util.stream.Collectors;
7
8import com.fasterxml.jackson.annotation.JsonCreator;
9import com.fasterxml.jackson.annotation.JsonProperty;
10
11import tudelft.healthpsychology.traumaontologies.questiontypes.TypedQuestion;
12import tudelft.healthpsychology.traumaontologies.questiontypes.SelectFromList;
13import tudelft.utilities.tree.Tree;
14
15/**
16 * This AnswerState searches the ontology class that is the best match to what
17 * the user has in mind. It tries to get as close as possible to the leaf nodes
18 * in the ontology, offering the user to choose between children nodes
19 * repeatedly.
20 *
21 * The state transitioning works as follows. The idea is that the initial state
22 * is with the most abstract {@link OntologyNode} fitting the expectations
23 * (typically "object" or "person").
24 *
25 * {@link #getOptions()} returns a set of more accurate ontology nodes that are
26 * children of the current node. By calling {@link #with(String)} with one of
27 * these nodes, the new state is that selected node. If tnat selected node is a
28 * leaf node, the state is final and the process is done.
29 *
30 * If with() is called with null it means "anders"/"other", which indicates the
31 * answer is not in the node set. If that is selected:
32 * <ul>
33 * <li>user did not yet select anything else and we are at depth >1. Then we
34 * decrease depth by 1.
35 * <li>user previously selected a child or we are at depth 1. Then we select the
36 * currently current node.
37 * </ul>
38 * immutable.
39 */
40public class OntologyAnswerState implements AnswerState {
41 /**
42 * Max nr of options given to the user to choose from
43 */
44 private static final int MAX_OPTIONS = 15;
45 private static final String OTHER = "anders";
46 private static final String IT = "het";
47
48 private final String nodelabel;
49 private final int depth;
50 private final boolean isFocused;
51 private final boolean isFinal;
52 private final String nameOfObject;
53
54 /**
55 *
56 * @param qnodelabel the {@link OntologyNode} that needs to be more precise.
57 * @param depth the depth at which the options are selected. 1 means
58 * direct children, 2 means children of the children, etc.
59 * This can be shallower than the maximum depth if user
60 * selects "other" indicating his choice is not in the
61 * exact list.
62 * @param isFocused true iff the user made a focusing step - he selected
63 * something else than "other" at some point.
64 * @param isfinal true iff this is a final state where the user made his
65 * choice. This can be either due to selecting a leaf
66 * node, or due to selecting other and then again the same
67 * subtype, and therefore has to be recorded separately.
68 * @pram nameOfObject a name of the object, to make the question to the user
69 * more specific which is especially relevant if this is part of a
70 * breath-first and the user entered some name or so some time before.
71 * If null, "het" ("it") is used.
72 */
73
74 @JsonCreator
75 public OntologyAnswerState(@JsonProperty("nodelabel") String qnodelabel,
76 @JsonProperty("depth") int depth,
77 @JsonProperty("isFocused") boolean isFocused,
78 @JsonProperty("isFinal") boolean isfinal,
79 @JsonProperty("nameOfObject") String nameOfObject) {
80 if (qnodelabel == null) {
81 throw new IllegalArgumentException("node must be not null");
82 }
83 this.nodelabel = qnodelabel;
84 this.depth = depth;
85 this.isFinal = isfinal;
86 this.isFocused = isFocused;
87 this.nameOfObject = nameOfObject == null ? IT : nameOfObject;
88 }
89
90 /**
91 * Convenience constructor that sets depth to maximum for initial state.
92 *
93 * @param qnode the node to create a new AnswerState for. This is initially
94 * the most abstract OntologyNode that fits the range of
95 * possibilities, eg #Persoon. The answer process gradually
96 * focuses on more detailed children of this node, and qnode
97 * will then change to this more detailed choice.
98 *
99 * The depth is set to the maximum possible depth with
100 * {@link #MAX_OPTIONS} answers.
101 * @pram nameOfObject a name of the object, to make the question to the user
102 * more specific which is especially relevant if this is part of a
103 * breath-first and the user entered some name or so some time before.
104 * If null, "het" ("it") is used.
105 */
106 public OntologyAnswerState(OntologyNode qnode, String nameOfObject) {
107 this(qnode.getLabel(), maximumDepth(qnode), false, false, nameOfObject);
108 }
109
110 @Override
111 public TypedQuestion getOptions(
112 Tree<String, Collection<Property>, OntologyNode> tree) {
113 if (isFinal)
114 return null;
115 OntologyNode node = tree.get(nodelabel);
116 List<String> optionstrings = node.getChildren(depth).stream()
117 .map(child -> child.getLabel()).collect(Collectors.toList());
118 optionstrings.add(OTHER);
119 String optionstring = optionstrings.toString().replace("[", "{")
120 .replace("]", "}");
121
122 return new SelectFromList(
123 "Kan U " + (isFocused ? "nog specifieker " : "")
124 + "aangeven: wat voor soort " + nodelabel + " was "
125 + nameOfObject + "? " + optionstring,
126 optionstrings, node.getLabel());
127 }
128
129 @Override
130 public OntologyAnswerState with(String answer,
131 Tree<String, Collection<Property>, OntologyNode> tree) {
132 if (OTHER.equals(answer)) {
133 return with((OntologyNode) null, tree);
134 }
135 OntologyNode root = tree.get(nodelabel);
136
137 Optional<OntologyNode> selection = root.getChildren(depth).stream()
138 .filter(node -> answer.equals(node.getLabel())).findFirst();
139 if (!selection.isPresent()) {
140 throw new IllegalArgumentException(
141 "Antwoord " + answer + " is geen optie");
142 }
143 return with(selection.get(), tree);
144
145 }
146
147 /**
148 *
149 * @param choice the {@link OntologyAnswerState} state that advances this to
150 * the next state. If null, it means the user could not make a
151 * choice ("other"). <br>
152 * The returned state is created as follows.
153 * <ol>
154 * <li>if choice=null:
155 * <ol>
156 * <li>if parent=null: new state = this and is final.
157 * <li>else new state = parent, depth =1
158 * </ol>
159 *
160 * <li>if choice is a leaf node: new state=choice and is final
161 * <li>if choice=node we just came from : new state=choice and
162 * final.
163 * <li>else new state = choice, depth = maximum with 15-node
164 * limit
165 * </ol>
166 * @return new {@link OntologyAnswerState} reflecting that the choice was
167 * made. The explanation is cleared, assuming that the user has read
168 * and understood the explanation after he gave an answer.
169 * @throws IllegalStateException if {@link #isFinal()}
170 */
171 public OntologyAnswerState with(OntologyNode choice,
172 Tree<String, Collection<Property>, OntologyNode> tree) {
173 if (isFinal) {
174 throw new IllegalStateException("state is final");
175 }
176 OntologyNode node = tree.get(nodelabel);
177 if (choice == null) {
178 if (depth <= 1 || isFocused) {
179 // take the current node as answer
180 return new OntologyAnswerState(nodelabel, depth, isFocused,
181 true, nameOfObject);
182 }
183 // zoom out
184 return new OntologyAnswerState(nodelabel, depth - 1, false, false,
185 nameOfObject);
186 }
187
188 // if choice has no children then this choice is also final.
189 return new OntologyAnswerState(choice.getLabel(), depth, true,
190 choice.getChildren().isEmpty(), nameOfObject);
191 }
192
193 /**
194 * @return the specific node label that was reached at this point.
195 */
196 public String getNode() {
197 return nodelabel;
198 }
199
200 /**
201 *
202 * @return the depth at which the options are selected. This can be
203 * shallower than the maximum depth, indicating we are restricting
204 * the user's answer options to non-leaf (more abstract) nodes.
205 */
206 public int getDepth() {
207 return depth;
208 }
209
210 @Override
211 public String toString() {
212 return "OntologyAnswerState[" + nodelabel + "," + isFocused + ","
213 + depth + "," + isFinal + "," + nameOfObject + "]";
214 }
215
216 /**
217 * @return the maximum depth achievable with at most MAX_ANSWERS. Assumes
218 * depth 1 and 2 exist and that #children <MAX_ANSWERS at depth 1.
219 */
220 private static int maximumDepth(OntologyNode node) {
221 List<OntologyNode> nodes = node.getChildren(1);
222 // assume at least 1 more child per depth step to put some upper limit
223 // invariant: up to given depth, there are less than MAX_OPTIONS
224 for (int depth = 1; depth < MAX_OPTIONS; depth++) {
225 List<OntologyNode> newnodes = node.getChildren(depth + 1);
226 // equals if we are effectively at the bottom of the tree.
227 if (newnodes.size() > MAX_OPTIONS || newnodes.equals(nodes))
228 return depth;
229 nodes = newnodes;
230 }
231 return MAX_OPTIONS;
232 }
233
234 @Override
235 public int hashCode() {
236 final int prime = 31;
237 int result = 1;
238 result = prime * result + depth;
239 result = prime * result + (isFinal ? 1231 : 1237);
240 result = prime * result + (isFocused ? 1231 : 1237);
241 result = prime * result
242 + ((nameOfObject == null) ? 0 : nameOfObject.hashCode());
243 result = prime * result
244 + ((nodelabel == null) ? 0 : nodelabel.hashCode());
245 return result;
246 }
247
248 @Override
249 public boolean equals(Object obj) {
250 if (this == obj)
251 return true;
252 if (obj == null)
253 return false;
254 if (getClass() != obj.getClass())
255 return false;
256 OntologyAnswerState other = (OntologyAnswerState) obj;
257 if (depth != other.depth)
258 return false;
259 if (isFinal != other.isFinal)
260 return false;
261 if (isFocused != other.isFocused)
262 return false;
263 if (nameOfObject == null) {
264 if (other.nameOfObject != null)
265 return false;
266 } else if (!nameOfObject.equals(other.nameOfObject))
267 return false;
268 if (nodelabel == null) {
269 if (other.nodelabel != null)
270 return false;
271 } else if (!nodelabel.equals(other.nodelabel))
272 return false;
273 return true;
274 }
275
276}
Note: See TracBrowser for help on using the repository browser.