Changeset 987 for trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/DefaultGuiElementSequenceDetectionRule.java
- Timestamp:
- 11/15/12 11:31:11 (12 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/DefaultGuiElementSequenceDetectionRule.java
r972 r987 12 12 13 13 /** 14 * This rule generates sequences depending on a GUI model. All actions occurring on the same GUI15 * element are put together in a sequence. All sequences and actions within a common panel are also16 * put together to a sequence. Through this, an event sequence gets a logical structure and shows,17 * which interactions that happen together also happen in the same GUI context.14 * This rule structures the task tree based on GUI elements of the GUI model. The rule can 15 * be provided with a filter for considered GUI elements. It generates sub sequences for any 16 * GUI element in the hierarchy matching the filter so that each sequence represents all 17 * interactions in a certain GUI element. 18 18 * 19 19 * @version $Revision: $ $Date: 18.03.2012$ … … 21 21 */ 22 22 public class DefaultGuiElementSequenceDetectionRule implements TemporalRelationshipRule { 23 24 /** 25 * <p> 26 * the GUI element filter to be applied or null if none is specified. 27 * </p> 28 */ 29 private List<Class<? extends IGUIElement>> guiElementFilter; 30 31 /** 32 * <p> 33 * instantiates the rule without a GUI element filter 34 * </p> 35 */ 36 DefaultGuiElementSequenceDetectionRule() { 37 this.guiElementFilter = null; 38 } 39 40 /** 41 * <p> 42 * instantiates the rule with a GUI element filter. Only those types given in the filter will 43 * be considered during the rule application. For all other types, no subsequences will be 44 * created. 45 * </p> 46 * 47 * @param guiElementFilter the GUI element filter to be applied 48 */ 49 DefaultGuiElementSequenceDetectionRule(List<Class<? extends IGUIElement>> guiElementFilter) { 50 this.guiElementFilter = guiElementFilter; 51 } 23 52 24 53 /* … … 39 68 40 69 RuleApplicationResult result = new RuleApplicationResult(); 41 42 IGUIElement lastGuiElement = null; 70 List<List<IGUIElement>> hierarchies = new ArrayList<List<IGUIElement>>(); 71 72 // collect information about the GUI hierarchy 73 int maxHierarchyDepth = 0; 74 IGUIElement guiElement; 75 List<IGUIElement> guiElements = new ArrayList<IGUIElement>(); 76 List<IGUIElement> hierarchy; 77 78 for (ITaskTreeNode child : parent.getChildren()) { 79 guiElement = getGuiElement(child); 80 guiElements.add(guiElement); 81 hierarchy = getGuiElementHierarchy(guiElement); 82 hierarchies.add(hierarchy); 83 if (hierarchy != null) { 84 maxHierarchyDepth = Math.max(maxHierarchyDepth, hierarchy.size()); 85 } 86 } 87 88 IGUIElement commonDenominator = getCommonDenominator(guiElements); 89 hierarchy = getGuiElementHierarchy(commonDenominator); 90 int initialHierarchyLevel = hierarchy != null ? hierarchy.size() : 0; 91 92 // now generate sub sequences for the different GUI elements. Start at the hierarchy 93 // level of the children of the common denominator to ensure, that different children are 94 // found. If this level is already the maximum hierarchy depth, we do not need to condense 95 // anything. 96 97 RuleApplicationStatus status; 98 if (initialHierarchyLevel < maxHierarchyDepth) { 99 status = generateSubSequences(parent, hierarchies, initialHierarchyLevel, finalize, 100 builder, nodeFactory, result); 101 } 102 else { 103 status = RuleApplicationStatus.RULE_NOT_APPLIED; 104 } 105 106 result.setRuleApplicationStatus(status); 107 108 return result; 109 } 110 111 /** 112 * <p> 113 * generates subsequences for all groups of children of the provided parent, that operate 114 * in different GUI elements at the provided hierarchy level. It will not generate a sub 115 * sequence for the last elements, if the rule application shall not finalize. 116 * </p> 117 * 118 * @param parent the parent node of which the children shall be grouped 119 * @param hierarchies the GUI hierarchies for the children of the parent 120 * @param hierarchyLevel the current hierarchy level to be considered 121 * @param maxHierarchyDepth the maximum hierarchy depth that may apply in this application 122 * @param finalize true, if the application shall be finalized, false else 123 * @param builder the builder to use for generating the tree structure 124 * @param nodeFactory the node factory to use for generating the tree structure 125 * @param result the result of the rule application to add newly created parent 126 * nodes to 127 * 128 * @return RULE_APPLICATION_FINISHED, if at least one subsequence was generated, 129 * RULE_APPLICATION_FEASIBLE, if the application shall not be finalized but some 130 * children could be condensed if further data was available, and RULE_NOT_APPLIED, 131 * if no subsequence was created and none is can be created, because no further 132 * data is expected 133 */ 134 private RuleApplicationStatus generateSubSequences(ITaskTreeNode parent, 135 List<List<IGUIElement>> hierarchies, 136 int hierarchyLevel, 137 boolean finalize, 138 ITaskTreeBuilder builder, 139 ITaskTreeNodeFactory nodeFactory, 140 RuleApplicationResult result) 141 { 142 IGUIElement currentParent = null; 143 List<IGUIElement> hierarchy; 144 int startingIndex = -1; 145 146 RuleApplicationStatus status = RuleApplicationStatus.RULE_NOT_APPLIED; 147 boolean subsequenceHasStarted = false; 148 boolean exceedingGuiHierarchyDepth = false; 149 boolean nextGuiElementDiffers = false; 150 151 currentParent = null; 152 startingIndex = -1; 153 43 154 int index = 0; 44 155 while (index < parent.getChildren().size()) { 45 ITaskTreeNode child = parent.getChildren().get(index); 46 IGUIElement currentGuiElement = getGUIElement(child); 47 if ((index > 0) && (!lastGuiElement.equals(currentGuiElement))) { 48 ReducableCommonDenominator commonDenominator = 49 getNextReducableCommonDenominator(parent, index - 1); 50 51 if (commonDenominator != null) { 52 // condense only if not all children would be condensed or if we can be sure, 53 // that there will be no further child that should be included in the condensed 54 // sequence 55 if ((commonDenominator.noOfTasks < parent.getChildren().size()) && 56 (!isOnGuiElementPath(commonDenominator.commonGuiElement, currentGuiElement))) 57 { 58 condenseTasksToSequence(parent, index, commonDenominator.noOfTasks, 59 builder, nodeFactory, result); 60 61 result.setRuleApplicationStatus 62 (RuleApplicationStatus.RULE_APPLICATION_FINISHED); 63 return result; 64 } 65 else { 66 // the common denominator is on the parent path of the next GUI element. 67 // Therefore, the current sequences is not finished yet. So break up. 68 result.setRuleApplicationStatus 69 (RuleApplicationStatus.RULE_APPLICATION_FEASIBLE); 70 } 71 } 72 } 73 74 lastGuiElement = currentGuiElement; 156 hierarchy = hierarchies.get(index); 157 158 exceedingGuiHierarchyDepth = hierarchyLevel >= hierarchy.size(); 159 nextGuiElementDiffers = 160 subsequenceHasStarted && 161 (exceedingGuiHierarchyDepth || !currentParent.equals(hierarchy.get(hierarchyLevel))); 162 163 164 if (!subsequenceHasStarted && !exceedingGuiHierarchyDepth) { 165 currentParent = hierarchy.get(hierarchyLevel); 166 startingIndex = index; 167 subsequenceHasStarted = true; 168 } 169 else if (nextGuiElementDiffers) { 170 status = condenseSequence(parent, hierarchies, hierarchyLevel, startingIndex, 171 index - 1, builder, nodeFactory, result); 172 173 if (status != null) { 174 index = startingIndex + 1; 175 } 176 177 if (!exceedingGuiHierarchyDepth) { 178 currentParent = hierarchy.get(hierarchyLevel); 179 startingIndex = index; 180 subsequenceHasStarted = true; 181 } 182 else { 183 currentParent = null; 184 startingIndex = -1; 185 subsequenceHasStarted = false; 186 } 187 } 188 75 189 index++; 76 190 } 77 191 78 ReducableCommonDenominator commonDenominator = 79 getNextReducableCommonDenominator(parent, parent.getChildren().size() - 1); 80 81 if ((commonDenominator != null) && 82 (commonDenominator.noOfTasks < parent.getChildren().size())) 83 { 84 if (finalize) { 85 condenseTasksToSequence 86 (parent, index, commonDenominator.noOfTasks, builder, nodeFactory, result); 192 if (finalize) { 193 if (subsequenceHasStarted) { 194 status = condenseSequence 195 (parent, hierarchies, hierarchyLevel, startingIndex, 196 parent.getChildren().size() - 1, builder, nodeFactory, result); 197 } 198 else if (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED) { 199 status = RuleApplicationStatus.RULE_NOT_APPLIED; 200 } 201 } 202 else { 203 if ((currentParent != null) && 204 (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED)) 205 { 206 status = RuleApplicationStatus.RULE_APPLICATION_FEASIBLE; 207 } 208 } 209 210 return status; 211 } 212 213 /** 214 * <p> 215 * condenses a specified group of children on the provided parent to a subsequences and 216 * calls {@link #generateSubSequences(ITaskTreeNode, List, int, boolean, ITaskTreeBuilder, ITaskTreeNodeFactory, RuleApplicationResult)} 217 * for the newly created subsequence. The method does not condense subgroups consisting of 218 * only one child which is already a sequence. 219 * </p> 220 * 221 * @param parent the parent task of which children shall be condensed 222 * @param hierarchies the GUI element hierarchies of the children of the parent 223 * @param hierarchyLevel the currently considered GUI element hierarchy level 224 * @param startIndex the index of the first child belonging to the subgroup 225 * @param endIndex the index of the last child belonging to the subgroup 226 * @param builder the builder to use for generating the tree structure 227 * @param nodeFactory the node factory to use for generating the tree structure 228 * @param result the result of the rule application to add newly created parent nodes to 229 * 230 * @return RULE_APPLICATION_FINISHED, if at the subsequence was generated and RULE_NOT_APPLIED, 231 * if no subsequence was created, because only one child belonged to the group which 232 * was already a sequence 233 */ 234 private RuleApplicationStatus condenseSequence(ITaskTreeNode parent, 235 List<List<IGUIElement>> hierarchies, 236 int hierarchyLevel, 237 int startIndex, 238 int endIndex, 239 ITaskTreeBuilder builder, 240 ITaskTreeNodeFactory nodeFactory, 241 RuleApplicationResult result) 242 { 243 boolean onlyASingleChildToReduce = (endIndex - startIndex) == 0; 244 boolean singleChildIsSequence = onlyASingleChildToReduce && 245 parent.getChildren().get(startIndex) instanceof ISequence; 246 247 if (!onlyASingleChildToReduce || !singleChildIsSequence) { 248 ISequence sequence = nodeFactory.createNewSequence(); 249 250 List<List<IGUIElement>> subHierarchies = new ArrayList<List<IGUIElement>>(); 251 List<IGUIElement> newHierarchy = 252 hierarchies.get(startIndex).subList(0, hierarchyLevel + 1); 253 builder.setDescription(sequence, "interactions on " + 254 newHierarchy.get(newHierarchy.size() - 1).getStringIdentifier()); 255 256 for (int i = startIndex; i <= endIndex; i++) { 257 builder.addChild(sequence, parent.getChildren().get(startIndex)); 258 builder.removeChild((ISequence) parent, startIndex); 87 259 88 result.setRuleApplicationStatus(RuleApplicationStatus.RULE_APPLICATION_FINISHED); 89 90 return result; 91 } 92 else { 93 result.setRuleApplicationStatus(RuleApplicationStatus.RULE_APPLICATION_FEASIBLE); 94 } 95 } 96 97 return result; 98 } 99 100 /** 101 * <p> 102 * TODO: comment 103 * </p> 104 * 105 * @param guiElement 106 * @param detectedTasks 107 * @param parent 108 * @param index 109 * @param builder 110 * @param nodeFactory 111 * @return 112 */ 113 private void condenseTasksToSequence(ITaskTreeNode parent, 114 int parentIndex, 115 int noOfTasks, 116 ITaskTreeBuilder builder, 117 ITaskTreeNodeFactory nodeFactory, 118 RuleApplicationResult result) 119 { 120 ISequence newSequence = nodeFactory.createNewSequence(); 121 for (int i = 0; i < noOfTasks; i++) { 122 builder.addChild(newSequence, parent.getChildren().get(parentIndex - noOfTasks)); 123 // remove exactly the same number of children from the parent. 124 builder.removeChild((ISequence) parent, parentIndex - noOfTasks); 125 } 126 127 builder.addChild((ISequence) parent, parentIndex - noOfTasks, newSequence); 128 result.addNewlyCreatedParentNode(newSequence); 129 } 130 131 /** 132 * <p> 133 * TODO: comment 134 * </p> 135 * 136 * @param detectedTasks 137 * @return 138 */ 139 private ReducableCommonDenominator getNextReducableCommonDenominator(ITaskTreeNode parent, 140 int childIndex) 141 { 142 ReducableCommonDenominator commonDenominator = null; 143 144 // a common denominator can only exist for at least two task tree nodes 145 if (childIndex > 0) { 146 // start with the last one 147 int pos = childIndex; 148 149 commonDenominator = new ReducableCommonDenominator(); 150 151 // check for further predecessors, if they match the same common denominator 152 IGUIElement currentCommonDenominator = null; 153 do { 154 if (--pos < 0) { 155 currentCommonDenominator = null; 156 } 157 else { 158 currentCommonDenominator = getCommonDenominator 159 (getGUIElement(parent.getChildren().get(pos)), 160 getGUIElement(parent.getChildren().get(pos + 1))); 161 } 162 163 if (commonDenominator.commonGuiElement == null) { 164 commonDenominator.commonGuiElement = currentCommonDenominator; 165 } 166 } 167 while ((commonDenominator.commonGuiElement != null) && 168 (commonDenominator.commonGuiElement.equals(currentCommonDenominator))); 169 170 if (commonDenominator.commonGuiElement != null) { 171 // pos points to the last element, that has not the same common denominator. 172 // This one must be subtracted from the task number as well 173 commonDenominator.noOfTasks = childIndex - pos; 174 } 175 else { 176 commonDenominator = null; 177 } 178 } 179 180 return commonDenominator; 181 } 182 183 /** 184 * <p> 185 * TODO: comment 186 * </p> 187 * 188 * @param child 189 * @return 190 */ 191 private IGUIElement getGUIElement(ITaskTreeNode node) { 192 List<IGUIElement> terminalGUIElements = new ArrayList<IGUIElement>(); 193 getTerminalGUIElements(node, terminalGUIElements); 194 return getCommonDenominator(terminalGUIElements); 195 } 196 197 /** 198 * <p> 199 * TODO: comment 200 * </p> 201 * 202 * @param detectedTaskGroups 203 * @return 204 */ 205 /*private IGUIElement getCommonDenominator(Stack<Task> detectedTasks, int start) { 206 List<IGUIElement> allGUIElements = new ArrayList<IGUIElement>(); 207 208 for (int i = start; i < detectedTasks.size(); i++) { 209 allGUIElements.add(detectedTasks.get(i).commonGuiElement); 210 } 211 212 return getCommonDenominator(allGUIElements); 213 }*/ 214 215 /** 216 * <p> 217 * TODO: comment 218 * </p> 219 * 220 * @param child 221 * @return 222 */ 223 private IGUIElement getCommonDenominator(IGUIElement guiElement1, IGUIElement guiElement2) { 224 List<IGUIElement> allGUIElements = new ArrayList<IGUIElement>(); 225 allGUIElements.add(guiElement1); 226 allGUIElements.add(guiElement2); 227 return getCommonDenominator(allGUIElements); 228 } 229 230 /** 231 * <p> 232 * TODO: comment 233 * </p> 234 * 235 * @param child 236 * @return 260 subHierarchies.add(hierarchies.remove(startIndex)); 261 } 262 263 builder.addChild((ISequence) parent, startIndex, sequence); 264 265 hierarchies.add(startIndex, newHierarchy); 266 267 generateSubSequences 268 (sequence, subHierarchies, hierarchyLevel + 1, true, builder, nodeFactory, result); 269 270 result.addNewlyCreatedParentNode(sequence); 271 272 return RuleApplicationStatus.RULE_APPLICATION_FINISHED; 273 } 274 else { 275 return null; 276 } 277 278 } 279 280 /** 281 * <p> 282 * return a common denominator for the provided list of GUI elements, i.e. a GUI element, that 283 * is part of the parent GUI hiearchy of all GUI elements in the list. If there is no common 284 * denominator, the method returns null. 285 * </p> 237 286 */ 238 287 private IGUIElement getCommonDenominator(List<IGUIElement> guiElements) { … … 245 294 IGUIElement guiElement = guiElements.get(0); 246 295 while (guiElement != null) { 247 commonDenominatorPath.add(0, guiElement); 296 if (guiElementMatchesConsideredTypes(guiElement)) { 297 commonDenominatorPath.add(0, guiElement); 298 } 248 299 guiElement = guiElement.getParent(); 300 } 301 302 if (commonDenominatorPath.size() == 0) { 303 return null; 249 304 } 250 305 … … 257 312 guiElement = guiElements.get(i); 258 313 while (guiElement != null) { 259 currentPath.add(0, guiElement); 314 if (guiElementMatchesConsideredTypes(guiElement)) { 315 currentPath.add(0, guiElement); 316 } 260 317 guiElement = guiElement.getParent(); 261 318 } … … 285 342 /** 286 343 * <p> 287 * TODO: comment 288 * </p> 289 * 290 * @param child 291 * @return 292 */ 293 private void getTerminalGUIElements(ITaskTreeNode node, List<IGUIElement> terminalGUIElements) { 344 * returns the GUI element on which all interactions of the provided task takes place. If 345 * the task is a simple event task its target is returned. If the task is a parent task 346 * of several children, the common denominator of the GUI elements of all its children is 347 * returned. The method returns null, if there is no common GUI element for all events 348 * represented by the provided task. 349 * </p> 350 */ 351 private IGUIElement getGuiElement(ITaskTreeNode node) { 352 if (node != null) { 353 List<IGUIElement> terminalGuiElements = new ArrayList<IGUIElement>(); 354 getTerminalGuiElements(node, terminalGuiElements); 355 return getCommonDenominator(terminalGuiElements); 356 } 357 else { 358 return null; 359 } 360 } 361 362 /** 363 * <p> 364 * recursive method calling itself to determine all terminal GUI elements of the provided 365 * task. The terminal GUI elements are stored in the provided list. 366 * </p> 367 */ 368 private void getTerminalGuiElements(ITaskTreeNode node, List<IGUIElement> terminalGuiElements) { 294 369 if (node instanceof IEventTask) { 295 370 if (((IEventTask) node).getEventTarget() instanceof IGUIElement) { 296 terminalGUIElements.add((IGUIElement) ((IEventTask) node).getEventTarget()); 371 IGUIElement terminalGuiElement = (IGUIElement) ((IEventTask) node).getEventTarget(); 372 terminalGuiElement = 373 searchHierarchyForGuiElementWithConsideredType(terminalGuiElement); 374 375 if (terminalGuiElement != null) { 376 terminalGuiElements.add(terminalGuiElement); 377 } 297 378 } 298 379 } 299 380 else { 300 381 for (ITaskTreeNode child : node.getChildren()) { 301 getTerminalGUIElements(child, terminalGUIElements); 302 } 303 } 304 } 305 306 /** 307 * <p> 308 * TODO: comment 309 * </p> 310 * 311 * @param currentCommonDenominator 312 * @param guiElement 313 * @return 314 */ 315 private boolean isOnGuiElementPath(IGUIElement potentialPathElement, IGUIElement child) { 316 IGUIElement guiElement = child; 317 318 while (guiElement != null) { 319 if (guiElement.equals(potentialPathElement)) { 320 return true; 321 } 322 guiElement = guiElement.getParent(); 323 } 324 325 return false; 326 } 327 328 /** 329 * 330 */ 331 private static class ReducableCommonDenominator { 332 333 /** the GUI element being the common denominator */ 334 private IGUIElement commonGuiElement; 335 336 /** the number of tasks that match the common denominator */ 337 private int noOfTasks; 338 339 /* (non-Javadoc) 340 * @see java.lang.Object#toString() 341 */ 342 @Override 343 public String toString() { 344 return noOfTasks + " tasks on " + commonGuiElement; 345 } 346 347 } 382 getTerminalGuiElements(child, terminalGuiElements); 383 } 384 } 385 } 386 387 /** 388 * <p> 389 * returns a list of GUI elements that represents the whole GUI element hierarchy of the 390 * provided GUI element. The method considers the GUI element filter applied by this rule. 391 * </p> 392 */ 393 private List<IGUIElement> getGuiElementHierarchy(IGUIElement guiElement) { 394 IGUIElement element = guiElement; 395 396 if (!guiElementMatchesConsideredTypes(element)) { 397 element = searchHierarchyForGuiElementWithConsideredType(element); 398 } 399 400 List<IGUIElement> hierarchy = new ArrayList<IGUIElement>(); 401 402 while (element != null) { 403 hierarchy.add(0, element); 404 element = searchHierarchyForGuiElementWithConsideredType(element.getParent()); 405 } 406 407 if (hierarchy.size() > 0) { 408 return hierarchy; 409 } 410 else { 411 return null; 412 } 413 } 414 415 /** 416 * <p> 417 * returns for a given GUI element the next GUI element in the upper GUI element hierarchy 418 * that matches the GUI element filter of the rule. If the provided GUI element already 419 * matches the filter, it is returned directly. 420 * </p> 421 */ 422 private IGUIElement searchHierarchyForGuiElementWithConsideredType(IGUIElement guiElement) { 423 IGUIElement returnValue = guiElement; 424 425 while ((returnValue != null) && !guiElementMatchesConsideredTypes(returnValue)) { 426 returnValue = returnValue.getParent(); 427 } 428 429 return returnValue; 430 } 431 432 /** 433 * <p> 434 * checks if the provided GUI element matches the GUI element filter applied by the rule. 435 * </p> 436 */ 437 private boolean guiElementMatchesConsideredTypes(IGUIElement guiElement) { 438 if (guiElementFilter == null) { 439 return true; 440 } 441 else { 442 for (Class<? extends IGUIElement> clazz : guiElementFilter) { 443 if (clazz.isInstance(guiElement)) { 444 return true; 445 } 446 } 447 448 return false; 449 } 450 } 451 348 452 }
Note: See TracChangeset
for help on using the changeset viewer.