1 | // Copyright 2012 Georg-August-Universität Göttingen, Germany |
---|
2 | // |
---|
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
---|
4 | // you may not use this file except in compliance with the License. |
---|
5 | // You may obtain a copy of the License at |
---|
6 | // |
---|
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
---|
8 | // |
---|
9 | // Unless required by applicable law or agreed to in writing, software |
---|
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
---|
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
---|
12 | // See the License for the specific language governing permissions and |
---|
13 | // limitations under the License. |
---|
14 | |
---|
15 | package de.ugoe.cs.autoquest.tasktrees.temporalrelation.utils; |
---|
16 | |
---|
17 | import java.io.PrintStream; |
---|
18 | import java.util.HashSet; |
---|
19 | import java.util.LinkedList; |
---|
20 | import java.util.List; |
---|
21 | import java.util.Set; |
---|
22 | |
---|
23 | import de.ugoe.cs.autoquest.eventcore.gui.Scroll; |
---|
24 | import de.ugoe.cs.autoquest.tasktrees.temporalrelation.TaskInstanceComparator; |
---|
25 | import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskTraversingVisitor; |
---|
26 | import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask; |
---|
27 | import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; |
---|
28 | import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration; |
---|
29 | import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship; |
---|
30 | import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection; |
---|
31 | import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship; |
---|
32 | import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask; |
---|
33 | import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance; |
---|
34 | import de.ugoe.cs.autoquest.tasktrees.treeifc.ITemporalRelationship; |
---|
35 | import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskPath; |
---|
36 | import difflib.Chunk; |
---|
37 | import difflib.Delta; |
---|
38 | import difflib.Patch; |
---|
39 | |
---|
40 | /** |
---|
41 | * <p> |
---|
42 | * This class represents a pair of tasks and their corresponding similarity level, respectively, |
---|
43 | * their diff level. In addition, it contains the task traversal, i.e., the flattened variants |
---|
44 | * of the compared tasks. It provides static methods to compare two tasks and get an object of this |
---|
45 | * class as result. Furthermore, it allows to adapt the contained traversals so that unmergable |
---|
46 | * subtasks are not traversed. |
---|
47 | * </p> |
---|
48 | * |
---|
49 | * @author Patrick Harms |
---|
50 | */ |
---|
51 | public class SimilarTasks { |
---|
52 | |
---|
53 | /** |
---|
54 | * result of the string like comparison of both task traversals |
---|
55 | */ |
---|
56 | private Patch patch; |
---|
57 | |
---|
58 | /** |
---|
59 | * the diff level resulting from the comparison |
---|
60 | */ |
---|
61 | private int diffLevel; |
---|
62 | |
---|
63 | /** |
---|
64 | * the first compared task |
---|
65 | */ |
---|
66 | private ITask leftHandSide; |
---|
67 | |
---|
68 | /** |
---|
69 | * the second compare task |
---|
70 | */ |
---|
71 | private ITask rightHandSide; |
---|
72 | |
---|
73 | /** |
---|
74 | * the traversal of the first task |
---|
75 | */ |
---|
76 | private TaskTraversal leftHandSideTraversal; |
---|
77 | |
---|
78 | /** |
---|
79 | * the traversal of the second task |
---|
80 | */ |
---|
81 | private TaskTraversal rightHandSideTraversal; |
---|
82 | |
---|
83 | /** |
---|
84 | * initialized a pair of tasks and calculates its diff level |
---|
85 | */ |
---|
86 | public SimilarTasks(Patch patch, |
---|
87 | ITask task1, |
---|
88 | TaskTraversal traversal1, |
---|
89 | ITask task2, |
---|
90 | TaskTraversal traversal2) |
---|
91 | { |
---|
92 | this.leftHandSide = task1; |
---|
93 | this.leftHandSideTraversal = traversal1; |
---|
94 | this.rightHandSide = task2; |
---|
95 | this.rightHandSideTraversal = traversal2; |
---|
96 | |
---|
97 | if (patch != null) { |
---|
98 | this.patch = patch; |
---|
99 | this.diffLevel = getDiffLevel(patch, traversal1, traversal2); |
---|
100 | } |
---|
101 | else { |
---|
102 | this.diffLevel = 100; |
---|
103 | } |
---|
104 | } |
---|
105 | |
---|
106 | /** |
---|
107 | * static convenience method to compare two tasks |
---|
108 | */ |
---|
109 | public static SimilarTasks compareTasks(ITask task1, |
---|
110 | ITask task2, |
---|
111 | TaskInstanceComparator comparator) |
---|
112 | { |
---|
113 | return compareTasks(task1, task2, comparator, null, true /* never set to false as a common |
---|
114 | path may occur several times |
---|
115 | at distinct places in a traversal |
---|
116 | and once it may be part of a delta |
---|
117 | and once not */); |
---|
118 | } |
---|
119 | |
---|
120 | /** |
---|
121 | * static convenience method to compare two tasks but ignoring a given set of subtasks when |
---|
122 | * creating the traversals. Furhtermore, it can be defined, if subtasks occurring in both |
---|
123 | * similar tasks are traversed or not. |
---|
124 | */ |
---|
125 | public static SimilarTasks compareTasks(ITask task1, |
---|
126 | ITask task2, |
---|
127 | TaskInstanceComparator comparator, |
---|
128 | List<TaskPath> pathsNotToTraverse, |
---|
129 | boolean traverseCommonPaths) |
---|
130 | { |
---|
131 | Set<ITask> commonInvolvedTasks; |
---|
132 | |
---|
133 | if (traverseCommonPaths) { |
---|
134 | commonInvolvedTasks = new HashSet<ITask>(); |
---|
135 | } |
---|
136 | else { |
---|
137 | commonInvolvedTasks = getCommonInvolvedTasks(task1, task2); |
---|
138 | } |
---|
139 | |
---|
140 | TaskTraversal traversal1 = |
---|
141 | TaskTraversal.getTraversal(task1, commonInvolvedTasks, pathsNotToTraverse); |
---|
142 | |
---|
143 | TaskTraversal traversal2 = |
---|
144 | TaskTraversal.getTraversal(task2, commonInvolvedTasks, pathsNotToTraverse); |
---|
145 | |
---|
146 | if ((traversal1.size() <= 0) || (traversal2.size() <= 0)) { |
---|
147 | return null; |
---|
148 | } |
---|
149 | |
---|
150 | Patch patch = new TaskTraversalMyersDiff(comparator).getDiff(traversal1, traversal2); |
---|
151 | |
---|
152 | SimilarTasks result = new SimilarTasks(patch, task1, traversal1, task2, traversal2); |
---|
153 | |
---|
154 | return result; |
---|
155 | } |
---|
156 | |
---|
157 | |
---|
158 | /** |
---|
159 | * This method checks for mergability of the provided similar tasks and if this is not given, |
---|
160 | * it reduces the traversals by ignoring interleaving iterations and other situations until |
---|
161 | * the remaining traversals can be the basis of a merge. |
---|
162 | */ |
---|
163 | public static SimilarTasks getMergableLevelOfSimilarity(SimilarTasks source, |
---|
164 | TaskInstanceComparator comparator) |
---|
165 | { |
---|
166 | SimilarTasks similarTasks = source; |
---|
167 | |
---|
168 | List<TaskPath> pathsToIgnore = similarTasks.getPathsToIgnore(); |
---|
169 | |
---|
170 | int prevPathsToIgnoreCount = 0; |
---|
171 | |
---|
172 | do { |
---|
173 | prevPathsToIgnoreCount = pathsToIgnore.size(); |
---|
174 | |
---|
175 | similarTasks = compareTasks |
---|
176 | (similarTasks.getLeftHandSide(), similarTasks.getRightHandSide(), |
---|
177 | comparator, pathsToIgnore, true); |
---|
178 | |
---|
179 | similarTasks.dump(System.out); |
---|
180 | |
---|
181 | List<TaskPath> furtherPathsToIgnore = similarTasks.getPathsToIgnore(); |
---|
182 | |
---|
183 | for (TaskPath path : furtherPathsToIgnore) { |
---|
184 | System.out.println(path); |
---|
185 | } |
---|
186 | |
---|
187 | for (TaskPath pathToAdd : furtherPathsToIgnore) { |
---|
188 | boolean found = false; |
---|
189 | for (TaskPath pathToIgnore : pathsToIgnore) { |
---|
190 | if (pathToAdd.equals(pathToIgnore)) { |
---|
191 | found = true; |
---|
192 | break; |
---|
193 | } |
---|
194 | } |
---|
195 | |
---|
196 | if (!found) { |
---|
197 | pathsToIgnore.add(pathToAdd); |
---|
198 | } |
---|
199 | } |
---|
200 | } |
---|
201 | while (pathsToIgnore.size() > prevPathsToIgnoreCount); |
---|
202 | |
---|
203 | return similarTasks; |
---|
204 | } |
---|
205 | |
---|
206 | /** |
---|
207 | * checks if the delta between the two traversals is at their border or not |
---|
208 | */ |
---|
209 | public boolean isInBetweenDifference() { |
---|
210 | List<Delta> deltas = patch.getDeltas(); |
---|
211 | |
---|
212 | for (Delta delta : deltas) { |
---|
213 | int origPos = delta.getOriginal().getPosition(); |
---|
214 | int origSize = delta.getOriginal().size(); |
---|
215 | int revPos = delta.getRevised().getPosition(); |
---|
216 | int revSize = delta.getRevised().size(); |
---|
217 | |
---|
218 | if ((origPos <= 0) || (revPos <= 0)) { |
---|
219 | // optional must not be the beginning of the task |
---|
220 | return false; |
---|
221 | } |
---|
222 | else if ((delta.getType() == Delta.TYPE.INSERT) && |
---|
223 | ((revPos + revSize) >= rightHandSideTraversal.size())) |
---|
224 | { |
---|
225 | // optional must not be the end of the right hand side task |
---|
226 | return false; |
---|
227 | } |
---|
228 | else if ((delta.getType() == Delta.TYPE.DELETE) && |
---|
229 | ((origPos + origSize) >= leftHandSideTraversal.size())) |
---|
230 | { |
---|
231 | // optional must not be the end of the left hand side task |
---|
232 | return false; |
---|
233 | } |
---|
234 | else if ((delta.getType() == Delta.TYPE.CHANGE) && |
---|
235 | (((revPos + revSize) >= rightHandSideTraversal.size()) || |
---|
236 | ((origPos + origSize) >= leftHandSideTraversal.size()))) |
---|
237 | { |
---|
238 | // selection must not be the end of the left hand or right hand side task |
---|
239 | return false; |
---|
240 | } |
---|
241 | } |
---|
242 | |
---|
243 | return deltas.size() > 0; |
---|
244 | } |
---|
245 | |
---|
246 | /** |
---|
247 | * @return the patch |
---|
248 | */ |
---|
249 | public Patch getPatch() { |
---|
250 | return patch; |
---|
251 | } |
---|
252 | |
---|
253 | /** |
---|
254 | * @return the diff level |
---|
255 | */ |
---|
256 | public int getDiffLevel() { |
---|
257 | return diffLevel; |
---|
258 | } |
---|
259 | |
---|
260 | /** |
---|
261 | * @return the first task of the comparison |
---|
262 | */ |
---|
263 | public ITask getLeftHandSide() { |
---|
264 | return leftHandSide; |
---|
265 | } |
---|
266 | |
---|
267 | /** |
---|
268 | * @return the second task of the comparison |
---|
269 | */ |
---|
270 | public ITask getRightHandSide() { |
---|
271 | return rightHandSide; |
---|
272 | } |
---|
273 | |
---|
274 | /** |
---|
275 | * @return the traversal of the first task of the comparison |
---|
276 | */ |
---|
277 | public TaskTraversal getLeftHandSideTraversal() { |
---|
278 | return leftHandSideTraversal; |
---|
279 | } |
---|
280 | |
---|
281 | /** |
---|
282 | * @return the traversal of the second task of the comparison |
---|
283 | */ |
---|
284 | public TaskTraversal getRightHandSideTraversal() { |
---|
285 | return rightHandSideTraversal; |
---|
286 | } |
---|
287 | |
---|
288 | /** |
---|
289 | * returns paths to subtasks, that can not be merged. |
---|
290 | */ |
---|
291 | public List<TaskPath> getPathsToIgnore() { |
---|
292 | List<TaskPath> result = new LinkedList<TaskPath>(); |
---|
293 | Delta singleChangeDelta = null; |
---|
294 | |
---|
295 | if ((patch.getDeltas().size() == 1) && |
---|
296 | (patch.getDeltas().get(0).getType() == Delta.TYPE.CHANGE)) |
---|
297 | { |
---|
298 | singleChangeDelta = patch.getDeltas().get(0); |
---|
299 | } |
---|
300 | |
---|
301 | // if it is a full change, then the parent of either side must be fully ignored |
---|
302 | if (singleChangeDelta != null) { |
---|
303 | if ((singleChangeDelta.getOriginal().getPosition() == 0) && |
---|
304 | (singleChangeDelta.getOriginal().size() == leftHandSideTraversal.size())) |
---|
305 | { |
---|
306 | TaskPath path = new TaskPath(); |
---|
307 | path.add(leftHandSide, 0); |
---|
308 | result.add(path); |
---|
309 | } |
---|
310 | |
---|
311 | if ((singleChangeDelta.getRevised().getPosition() == 0) && |
---|
312 | (singleChangeDelta.getRevised().size() == rightHandSideTraversal.size())) |
---|
313 | { |
---|
314 | TaskPath path = new TaskPath(); |
---|
315 | path.add(rightHandSide, 0); |
---|
316 | result.add(path); |
---|
317 | } |
---|
318 | } |
---|
319 | |
---|
320 | if (result.size() < 2) { |
---|
321 | // if we do not already ignore both root tasks, check for selections and interleaving |
---|
322 | // iterations that have to be ignored if they belong to a delta |
---|
323 | for (Delta delta : patch.getDeltas()) { |
---|
324 | //getSelections(delta, result); |
---|
325 | getPathsToRealIntersectingIterations(delta, result); |
---|
326 | getNotFullyInterleavingIteration(delta, result); |
---|
327 | getPathsToParentTasksFullyInsideDelta(delta, result); |
---|
328 | } |
---|
329 | |
---|
330 | // check if there are common subtasks on both sides that lie outside any delta and |
---|
331 | // that hence should be ignored (especially when performing flattening of task |
---|
332 | // instances later with the goal to preserve as many subtasks and respective instances |
---|
333 | int deltaIndex = 0; |
---|
334 | int leftTraversalIndex = 0; |
---|
335 | int rightTraversalIndex = 0; |
---|
336 | Set<ITask> commonInvolvedTasks = getCommonInvolvedTasks(leftHandSide, rightHandSide); |
---|
337 | |
---|
338 | do { |
---|
339 | Delta delta = deltaIndex < patch.getDeltas().size() |
---|
340 | ? patch.getDeltas().get(deltaIndex++) : null; |
---|
341 | |
---|
342 | int nextLeftTraversalIndex = leftHandSideTraversal.size(); |
---|
343 | int nextRightTraversalIndex = rightHandSideTraversal.size(); |
---|
344 | |
---|
345 | if (delta != null) { |
---|
346 | nextLeftTraversalIndex = delta.getOriginal().getPosition() + 1; |
---|
347 | nextRightTraversalIndex = delta.getRevised().getPosition() + 1; |
---|
348 | |
---|
349 | if (delta.getType() == Delta.TYPE.INSERT) { |
---|
350 | nextLeftTraversalIndex--; |
---|
351 | } |
---|
352 | else if (delta.getType() == Delta.TYPE.DELETE) { |
---|
353 | nextRightTraversalIndex--; |
---|
354 | } |
---|
355 | } |
---|
356 | |
---|
357 | TaskPath[] leftSubTraversal = |
---|
358 | leftHandSideTraversal.subList(leftTraversalIndex, nextLeftTraversalIndex); |
---|
359 | |
---|
360 | for (TaskPath path : leftSubTraversal) { |
---|
361 | for (int i = 0; i < path.size(); i++) { |
---|
362 | if (commonInvolvedTasks.contains(path.getTask(i))) { |
---|
363 | // found a candidate. Check if the candidate does not span more than |
---|
364 | // the sub traversal |
---|
365 | TaskPath parentPath = path.subPath(0, i + 1); |
---|
366 | int[] indexes = leftHandSideTraversal.getIndexesRootedBy(parentPath); |
---|
367 | |
---|
368 | if ((indexes[0] >= leftTraversalIndex) && |
---|
369 | (indexes[1] < nextLeftTraversalIndex)) |
---|
370 | { |
---|
371 | result.add(parentPath); |
---|
372 | break; // breaks only the path but the rest of the traversal is |
---|
373 | // checked too |
---|
374 | } |
---|
375 | } |
---|
376 | } |
---|
377 | } |
---|
378 | |
---|
379 | TaskPath[] rightSubTraversal = |
---|
380 | rightHandSideTraversal.subList(rightTraversalIndex, nextRightTraversalIndex); |
---|
381 | |
---|
382 | for (TaskPath path : rightSubTraversal) { |
---|
383 | for (int i = 0; i < path.size(); i++) { |
---|
384 | if (commonInvolvedTasks.contains(path.getTask(i))) { |
---|
385 | // found a candidate. Check if the candidate does not span more than |
---|
386 | // the sub traversal |
---|
387 | TaskPath parentPath = path.subPath(0, i + 1); |
---|
388 | int[] indexes = rightHandSideTraversal.getIndexesRootedBy(parentPath); |
---|
389 | |
---|
390 | if ((indexes[0] >= rightTraversalIndex) && |
---|
391 | (indexes[1] < nextRightTraversalIndex)) |
---|
392 | { |
---|
393 | result.add(parentPath); |
---|
394 | break; // breaks only the path but the rest of the traversal is |
---|
395 | // checked too |
---|
396 | } |
---|
397 | } |
---|
398 | } |
---|
399 | } |
---|
400 | |
---|
401 | leftTraversalIndex = nextLeftTraversalIndex; |
---|
402 | rightTraversalIndex = nextRightTraversalIndex; |
---|
403 | } |
---|
404 | while (leftTraversalIndex < leftHandSideTraversal.size()); |
---|
405 | |
---|
406 | } |
---|
407 | |
---|
408 | return result; |
---|
409 | } |
---|
410 | |
---|
411 | /** |
---|
412 | * |
---|
413 | */ |
---|
414 | public void dump(PrintStream out) { |
---|
415 | out.println(); |
---|
416 | out.print("similar tasks: left "); |
---|
417 | out.print(leftHandSide); |
---|
418 | out.print(" ("); |
---|
419 | out.print(leftHandSide.getInstances().size()); |
---|
420 | out.print(" instances), right "); |
---|
421 | out.print(rightHandSide); |
---|
422 | out.print(" ("); |
---|
423 | out.print(rightHandSide.getInstances().size()); |
---|
424 | out.print(" instances), diff level "); |
---|
425 | out.println(diffLevel); |
---|
426 | |
---|
427 | out.println("left traversal"); |
---|
428 | for (TaskPath path : leftHandSideTraversal.getTraversalPaths()) { |
---|
429 | out.println(path); |
---|
430 | } |
---|
431 | |
---|
432 | out.println("right traversal"); |
---|
433 | for (TaskPath path : rightHandSideTraversal.getTraversalPaths()) { |
---|
434 | out.println(path); |
---|
435 | } |
---|
436 | |
---|
437 | if (patch != null) { |
---|
438 | out.println("deltas:"); |
---|
439 | for (Delta delta : patch.getDeltas()) { |
---|
440 | out.println(delta); |
---|
441 | } |
---|
442 | } |
---|
443 | |
---|
444 | List<String> linesLeft = new LinkedList<String>(); |
---|
445 | TaskPath path = new TaskPath(); |
---|
446 | path.add(leftHandSide, 0); |
---|
447 | dumpToLineList(path, leftHandSideTraversal, linesLeft); |
---|
448 | path.removeLast(); |
---|
449 | |
---|
450 | List<String> linesRight = new LinkedList<String>(); |
---|
451 | path.add(rightHandSide, 0); |
---|
452 | dumpToLineList(path, rightHandSideTraversal, linesRight); |
---|
453 | |
---|
454 | |
---|
455 | /*System.out.println("\n#################################################"); |
---|
456 | for (int j = 0; ((j < linesLeft.size()) || (j < linesRight.size())); j++) { |
---|
457 | String left = j < linesLeft.size() ? linesLeft.get(j) : ""; |
---|
458 | String right = j < linesRight.size() ? linesRight.get(j) : ""; |
---|
459 | |
---|
460 | StringBuffer line = new StringBuffer(); |
---|
461 | line.append(j); |
---|
462 | line.append(":\t"); |
---|
463 | line.append(left); |
---|
464 | |
---|
465 | for (int k = line.length(); k < 60; k++) { |
---|
466 | line.append(' '); |
---|
467 | } |
---|
468 | |
---|
469 | line.append(right); |
---|
470 | System.out.println(line); |
---|
471 | }*/ |
---|
472 | |
---|
473 | // change lists to have the same length depending on the changes |
---|
474 | int lineIndexLeft = 0; |
---|
475 | int lineIndexRight = 0; |
---|
476 | int leftHandTraversalIndex = 0; |
---|
477 | while (leftHandTraversalIndex < leftHandSideTraversal.size()) { |
---|
478 | |
---|
479 | //System.out.println("\n" + lineIndexLeft + " " + lineIndexRight); |
---|
480 | |
---|
481 | Delta delta = null; |
---|
482 | |
---|
483 | for (Delta candidate : patch.getDeltas()) { |
---|
484 | if (candidate.getOriginal().getPosition() == leftHandTraversalIndex) { |
---|
485 | delta = candidate; |
---|
486 | break; |
---|
487 | } |
---|
488 | } |
---|
489 | |
---|
490 | if ((delta != null) && (delta.getType() == Delta.TYPE.DELETE)) { |
---|
491 | for (int j = 0; j < delta.getOriginal().size(); j++) { |
---|
492 | while (linesLeft.get(lineIndexLeft) != null) { |
---|
493 | lineIndexLeft++; |
---|
494 | } |
---|
495 | |
---|
496 | linesLeft.remove(lineIndexLeft); |
---|
497 | |
---|
498 | while (lineIndexRight < lineIndexLeft) { |
---|
499 | linesRight.add(lineIndexRight++, null); |
---|
500 | } |
---|
501 | |
---|
502 | leftHandTraversalIndex++; |
---|
503 | } |
---|
504 | |
---|
505 | linesLeft.add(lineIndexLeft++, "-------------------------------------"); |
---|
506 | linesRight.add(lineIndexRight++, "-------------------------------------"); |
---|
507 | } |
---|
508 | else if ((delta != null) && (delta.getType() == Delta.TYPE.INSERT)) { |
---|
509 | for (int j = 0; j < delta.getRevised().size(); j++) { |
---|
510 | while (linesRight.get(lineIndexRight) != null) { |
---|
511 | lineIndexRight++; |
---|
512 | } |
---|
513 | |
---|
514 | linesRight.remove(lineIndexRight); |
---|
515 | |
---|
516 | while (lineIndexLeft < lineIndexRight) { |
---|
517 | linesLeft.add(lineIndexLeft++, null); |
---|
518 | } |
---|
519 | } |
---|
520 | |
---|
521 | // ensure that what is equal is harmonized too (because after an insert always |
---|
522 | // comes something equal). The traversal index will be updated automatically, |
---|
523 | // too. |
---|
524 | delta = null; |
---|
525 | |
---|
526 | linesLeft.add(lineIndexLeft++, "-------------------------------------"); |
---|
527 | linesRight.add(lineIndexRight++, "-------------------------------------"); |
---|
528 | } |
---|
529 | |
---|
530 | if ((delta == null) || (delta.getType() == Delta.TYPE.CHANGE)) { |
---|
531 | int chunkSizeLeft = delta != null ? delta.getOriginal().size() : 1; |
---|
532 | int chunkSizeRight = delta != null ? delta.getRevised().size() : 1; |
---|
533 | |
---|
534 | int chunksHandledLeft = 0; |
---|
535 | int chunksHandledRight = 0; |
---|
536 | |
---|
537 | for (int j = 0; j < Math.max(chunkSizeLeft, chunkSizeRight); j++) { |
---|
538 | if (chunksHandledLeft < chunkSizeLeft) { |
---|
539 | while (linesLeft.get(lineIndexLeft) != null) { |
---|
540 | lineIndexLeft++; |
---|
541 | } |
---|
542 | |
---|
543 | linesLeft.remove(lineIndexLeft); |
---|
544 | chunksHandledLeft++; |
---|
545 | } |
---|
546 | |
---|
547 | if (chunksHandledRight < chunkSizeRight) { |
---|
548 | while (linesRight.get(lineIndexRight) != null) { |
---|
549 | lineIndexRight++; |
---|
550 | } |
---|
551 | |
---|
552 | linesRight.remove(lineIndexRight); |
---|
553 | chunksHandledRight++; |
---|
554 | } |
---|
555 | |
---|
556 | while (lineIndexLeft < lineIndexRight) { |
---|
557 | linesLeft.add(lineIndexLeft++, null); |
---|
558 | } |
---|
559 | while (lineIndexRight < lineIndexLeft) { |
---|
560 | linesRight.add(lineIndexRight++, null); |
---|
561 | } |
---|
562 | } |
---|
563 | |
---|
564 | linesLeft.add(lineIndexLeft++, "-------------------------------------"); |
---|
565 | linesRight.add(lineIndexRight++, "-------------------------------------"); |
---|
566 | |
---|
567 | leftHandTraversalIndex += delta != null ? delta.getOriginal().size() : 1; |
---|
568 | } |
---|
569 | |
---|
570 | /*System.out.println(delta + " " + leftHandTraversalIndex); |
---|
571 | System.out.println("\n#################################################"); |
---|
572 | for (int j = 0; ((j < linesLeft.size()) || (j < linesRight.size())); j++) { |
---|
573 | String left = j < linesLeft.size() ? linesLeft.get(j) : ""; |
---|
574 | String right = j < linesRight.size() ? linesRight.get(j) : ""; |
---|
575 | |
---|
576 | StringBuffer line = new StringBuffer(); |
---|
577 | line.append(j); |
---|
578 | line.append(":\t"); |
---|
579 | line.append(left); |
---|
580 | |
---|
581 | for (int k = line.length(); k < 60; k++) { |
---|
582 | line.append(' '); |
---|
583 | } |
---|
584 | |
---|
585 | line.append(right); |
---|
586 | System.out.println(line); |
---|
587 | }*/ |
---|
588 | } |
---|
589 | |
---|
590 | int indentLeft = 0; |
---|
591 | int indentRight = 0; |
---|
592 | |
---|
593 | for (int i = 0; i < linesLeft.size(); i++) { |
---|
594 | StringBuffer line = new StringBuffer(); |
---|
595 | String left = linesLeft.get(i); |
---|
596 | String right = linesRight.get(i); |
---|
597 | |
---|
598 | if (left != null) { |
---|
599 | if (left.indexOf('}') >= 0) { |
---|
600 | indentLeft -= 2; |
---|
601 | } |
---|
602 | |
---|
603 | for (int j = 0; j < indentLeft; j++) { |
---|
604 | line.append(' '); |
---|
605 | } |
---|
606 | |
---|
607 | if (left.indexOf('{') >= 0) { |
---|
608 | indentLeft += 2; |
---|
609 | } |
---|
610 | |
---|
611 | line.append(left); |
---|
612 | } |
---|
613 | |
---|
614 | if (right != null) { |
---|
615 | for (int j = line.length(); j < 100; j++) { |
---|
616 | line.append(' '); |
---|
617 | } |
---|
618 | if (right.indexOf('}') >= 0) { |
---|
619 | indentRight -= 2; |
---|
620 | } |
---|
621 | |
---|
622 | for (int j = 0; j < indentRight; j++) { |
---|
623 | line.append(' '); |
---|
624 | } |
---|
625 | |
---|
626 | if (right.indexOf('{') >= 0) { |
---|
627 | indentRight += 2; |
---|
628 | } |
---|
629 | |
---|
630 | line.append(right); |
---|
631 | } |
---|
632 | |
---|
633 | out.println(line); |
---|
634 | } |
---|
635 | } |
---|
636 | |
---|
637 | /** |
---|
638 | * |
---|
639 | */ |
---|
640 | private static Set<ITask> getCommonInvolvedTasks(ITask task1, ITask task2) { |
---|
641 | Set<ITask> result = getAllInvolvedTasks(task1); |
---|
642 | result.retainAll(getAllInvolvedTasks(task2)); |
---|
643 | return result; |
---|
644 | } |
---|
645 | |
---|
646 | /** |
---|
647 | * |
---|
648 | */ |
---|
649 | private static Set<ITask> getAllInvolvedTasks(ITask task) { |
---|
650 | final Set<ITask> result = new HashSet<ITask>(); |
---|
651 | |
---|
652 | task.accept(new DefaultTaskTraversingVisitor() { |
---|
653 | @Override |
---|
654 | public void visit(IEventTask eventTask) { |
---|
655 | result.add(eventTask); |
---|
656 | } |
---|
657 | |
---|
658 | @Override |
---|
659 | public void visit(IStructuringTemporalRelationship relationship) { |
---|
660 | result.add(relationship); |
---|
661 | super.visit(relationship); |
---|
662 | } |
---|
663 | |
---|
664 | @Override |
---|
665 | public void visit(IMarkingTemporalRelationship relationship) { |
---|
666 | result.add(relationship); |
---|
667 | super.visit(relationship); |
---|
668 | } |
---|
669 | }); |
---|
670 | |
---|
671 | return result; |
---|
672 | } |
---|
673 | |
---|
674 | /** |
---|
675 | * |
---|
676 | */ |
---|
677 | private void dumpToLineList(TaskPath path, |
---|
678 | TaskTraversal traversal, |
---|
679 | List<String> lines) |
---|
680 | { |
---|
681 | boolean isTraversalElement = false; |
---|
682 | |
---|
683 | for (TaskPath candidate : traversal.getTraversalPaths()) { |
---|
684 | if (path.equals(candidate)) { |
---|
685 | isTraversalElement = true; |
---|
686 | break; |
---|
687 | } |
---|
688 | } |
---|
689 | |
---|
690 | if (isTraversalElement) { |
---|
691 | lines.add(path.getLast().toString()); |
---|
692 | lines.add(null); |
---|
693 | /*ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); |
---|
694 | PrintStream out = new PrintStream(byteStream); |
---|
695 | |
---|
696 | new TaskTreeEncoder().encode(path.getLast(), out); |
---|
697 | |
---|
698 | out.close(); |
---|
699 | |
---|
700 | BufferedReader reader = new BufferedReader |
---|
701 | (new InputStreamReader(new ByteArrayInputStream(byteStream.toByteArray()))); |
---|
702 | |
---|
703 | String line = null; |
---|
704 | do { |
---|
705 | try { |
---|
706 | line = reader.readLine(); |
---|
707 | } |
---|
708 | catch (IOException e) { |
---|
709 | // should not happen so dump hard |
---|
710 | e.printStackTrace(); |
---|
711 | } |
---|
712 | lines.add(line != null ? line.trim() : null); |
---|
713 | } |
---|
714 | while (line != null);*/ |
---|
715 | |
---|
716 | } |
---|
717 | else { |
---|
718 | ITask task = path.getLast(); |
---|
719 | if (task instanceof ITemporalRelationship) { |
---|
720 | lines.add(task + " {"); |
---|
721 | |
---|
722 | if (task instanceof IStructuringTemporalRelationship) { |
---|
723 | List<ITask> children = |
---|
724 | ((IStructuringTemporalRelationship) task).getChildren(); |
---|
725 | |
---|
726 | for (int i = 0; i < children.size(); i++) { |
---|
727 | path.add(children.get(i), i); |
---|
728 | dumpToLineList(path, traversal, lines); |
---|
729 | path.removeLast(); |
---|
730 | } |
---|
731 | } |
---|
732 | else if (task instanceof IMarkingTemporalRelationship) { |
---|
733 | IMarkingTemporalRelationship relationship = |
---|
734 | (IMarkingTemporalRelationship) task; |
---|
735 | |
---|
736 | path.add(relationship.getMarkedTask(), 0); |
---|
737 | dumpToLineList(path, traversal, lines); |
---|
738 | path.removeLast(); |
---|
739 | } |
---|
740 | |
---|
741 | if (lines.get(lines.size() - 1) != null) { |
---|
742 | lines.add("}"); |
---|
743 | } |
---|
744 | else { |
---|
745 | lines.add(lines.size() - 1, "}"); |
---|
746 | } |
---|
747 | } |
---|
748 | else { |
---|
749 | lines.add(task + " {}"); |
---|
750 | } |
---|
751 | } |
---|
752 | } |
---|
753 | |
---|
754 | /** |
---|
755 | * |
---|
756 | */ |
---|
757 | /*@SuppressWarnings("unchecked") |
---|
758 | private void getSelections(Delta delta, List<TaskPath> result) { |
---|
759 | TaskPath path1 = getCommonPath((List<TaskPath>) delta.getOriginal().getLines()); |
---|
760 | TaskPath path2 = getCommonPath((List<TaskPath>) delta.getRevised().getLines()); |
---|
761 | |
---|
762 | for (int i = 0; i < path1.size(); i++) { |
---|
763 | if (path1.getTask(i) instanceof ISelection) { |
---|
764 | System.out.println("found selection to ignore"); |
---|
765 | System.out.println(path1.subPath(0, i + 1)); |
---|
766 | result.add(path1.subPath(0, i + 1)); |
---|
767 | break; |
---|
768 | } |
---|
769 | } |
---|
770 | |
---|
771 | for (int i = 0; i < path2.size(); i++) { |
---|
772 | if (path2.getTask(i) instanceof ISelection) { |
---|
773 | System.out.println("found selection to ignore"); |
---|
774 | System.out.println(path2.subPath(0, i + 1)); |
---|
775 | result.add(path2.subPath(0, i + 1)); |
---|
776 | break; |
---|
777 | } |
---|
778 | } |
---|
779 | }*/ |
---|
780 | |
---|
781 | /** |
---|
782 | * |
---|
783 | */ |
---|
784 | @SuppressWarnings("unchecked") |
---|
785 | private void getPathsToRealIntersectingIterations(Delta delta, List<TaskPath> paths) { |
---|
786 | TaskPath path1 = getCommonPath((List<TaskPath>) delta.getOriginal().getLines()); |
---|
787 | TaskPath path2 = getCommonPath((List<TaskPath>) delta.getRevised().getLines()); |
---|
788 | |
---|
789 | // both paths denote those parent tasks that contain the delta. |
---|
790 | // But there may be parent tasks still contained in the path that are iterations and |
---|
791 | // that are disjoint with a parent in the other path. Those iterations need to be |
---|
792 | // considered, as otherwise a created selection would be against the associative law of |
---|
793 | // iterations. Hence, we search for the topmost iteration on both paths that is disjoint |
---|
794 | // with a parent node in the respective other path. |
---|
795 | |
---|
796 | for (int i = 0; i < path1.size(); i++) { |
---|
797 | TaskPath path1Prefix = path1.subPath(0, i + 1); |
---|
798 | int[] idxsPath1 = leftHandSideTraversal.getIndexesRootedBy(path1Prefix); |
---|
799 | |
---|
800 | // normalize the indexes to be 0 the index of the delta. Everything left of it |
---|
801 | // is smaller 0, everything right of it, larger. |
---|
802 | idxsPath1[0] -= delta.getOriginal().getPosition(); |
---|
803 | idxsPath1[1] -= delta.getOriginal().getPosition() + delta.getOriginal().size() - 1; |
---|
804 | |
---|
805 | for (int j = 0; j < path2.size(); j++) { |
---|
806 | TaskPath path2Prefix = path2.subPath(0, j + 1); |
---|
807 | int[] idxsPath2 = rightHandSideTraversal.getIndexesRootedBy(path2Prefix); |
---|
808 | |
---|
809 | // normalize the indexes to be 0 the index of the delta. Everything left of it |
---|
810 | // is smaller 0, everything right of it, larger. |
---|
811 | idxsPath2[0] -= delta.getRevised().getPosition(); |
---|
812 | idxsPath2[1] -= |
---|
813 | delta.getRevised().getPosition() + delta.getRevised().size() - 1; |
---|
814 | |
---|
815 | // now compare the indexes and check, if the sublists are real subsets of each |
---|
816 | // other. If not, they only have a real common subset but there is at least one |
---|
817 | // non shared element on both sides. If so, and one is an iteration, we |
---|
818 | // have to use it as elements to be selected |
---|
819 | if (((idxsPath1[0] < idxsPath2[0]) && (idxsPath1[1] < idxsPath2[1])) || |
---|
820 | ((idxsPath1[0] > idxsPath2[0]) && (idxsPath1[1] > idxsPath2[1]))) |
---|
821 | { |
---|
822 | if ((path1.get(i).getTask() instanceof IIteration) && |
---|
823 | (path2.get(j).getTask() instanceof IIteration)) |
---|
824 | { |
---|
825 | System.out.println("found paths to real intersecting parents"); |
---|
826 | int[] indexBuf = leftHandSideTraversal.getIndexesRootedBy(path1Prefix); |
---|
827 | System.out.println(idxsPath1[0] + "(" + indexBuf[0] + ") " + idxsPath1[1] + |
---|
828 | "(" + indexBuf[1] + ") " + path1Prefix); |
---|
829 | indexBuf = rightHandSideTraversal.getIndexesRootedBy(path2Prefix); |
---|
830 | System.out.println(idxsPath2[0] + "(" + indexBuf[0] + ") " + idxsPath2[1] + |
---|
831 | "(" + indexBuf[1] + ") " + path2Prefix); |
---|
832 | |
---|
833 | paths.add(path1Prefix); |
---|
834 | paths.add(path2Prefix); |
---|
835 | return; |
---|
836 | } |
---|
837 | } |
---|
838 | } |
---|
839 | } |
---|
840 | } |
---|
841 | |
---|
842 | /** |
---|
843 | * |
---|
844 | */ |
---|
845 | private TaskPath getCommonPath(List<TaskPath> paths) { |
---|
846 | TaskPath commonPath; |
---|
847 | |
---|
848 | if (paths.size() > 0) { |
---|
849 | commonPath = new TaskPath(paths.get(0)); |
---|
850 | |
---|
851 | for (int i = 1; i < paths.size(); i++) { |
---|
852 | TaskPath comparePath = paths.get(i); |
---|
853 | for (int j = 0; (j < commonPath.size()) && (j < comparePath.size()); j++) { |
---|
854 | if (!commonPath.get(j).equals(comparePath.get(j))) { |
---|
855 | while (commonPath.size() > j) { |
---|
856 | commonPath.remove(j); |
---|
857 | } |
---|
858 | break; |
---|
859 | } |
---|
860 | } |
---|
861 | } |
---|
862 | } |
---|
863 | else { |
---|
864 | commonPath = new TaskPath(); |
---|
865 | } |
---|
866 | |
---|
867 | return commonPath; |
---|
868 | } |
---|
869 | |
---|
870 | /** |
---|
871 | * |
---|
872 | */ |
---|
873 | private void getNotFullyInterleavingIteration(Delta delta, List<TaskPath> result) { |
---|
874 | getNotFullyInterleavingIterations(delta.getOriginal(), leftHandSideTraversal, result); |
---|
875 | getNotFullyInterleavingIterations(delta.getRevised(), rightHandSideTraversal, result); |
---|
876 | } |
---|
877 | |
---|
878 | /** |
---|
879 | * If a chunk has a minimum number of 2 entries and at least one of them belongs to an iteration |
---|
880 | * the does not cover the other and also expands to preceding or succeeding paths in the |
---|
881 | * traversal, we can no merge the situation. |
---|
882 | */ |
---|
883 | private void getNotFullyInterleavingIterations(Chunk chunk, |
---|
884 | TaskTraversal traversal, |
---|
885 | List<TaskPath> result) |
---|
886 | { |
---|
887 | @SuppressWarnings("unchecked") |
---|
888 | List<TaskPath> paths = (List<TaskPath>) chunk.getLines(); |
---|
889 | |
---|
890 | TaskPath commonPath = getCommonPath(paths); |
---|
891 | |
---|
892 | if (paths.size() > 1) { |
---|
893 | TaskPath firstPath = paths.get(0); |
---|
894 | TaskPath lastPath = paths.get(paths.size() - 1); |
---|
895 | |
---|
896 | for (int i = commonPath.size(); i < firstPath.size(); i++) { |
---|
897 | if (firstPath.get(i).getTask() instanceof IIteration) { |
---|
898 | // check, if the iteration covers things preceding the delta but not the whole |
---|
899 | // delta |
---|
900 | int[] indexes = traversal.getIndexesRootedBy(firstPath.subPath(0, i + 1)); |
---|
901 | |
---|
902 | if ((indexes[0] < chunk.getPosition()) && // starts before the delta |
---|
903 | (indexes[1] < (chunk.getPosition() + chunk.size() - 1))) // does not cover delta |
---|
904 | { |
---|
905 | result.add(firstPath.subPath(0, i + 1)); |
---|
906 | break; |
---|
907 | } |
---|
908 | } |
---|
909 | } |
---|
910 | |
---|
911 | for (int i = commonPath.size(); i < lastPath.size(); i++) { |
---|
912 | if (lastPath.get(i).getTask() instanceof IIteration) { |
---|
913 | // check, if the iteration covers things preceding the delta but not the whole |
---|
914 | // delta |
---|
915 | int[] indexes = traversal.getIndexesRootedBy(lastPath.subPath(0, i + 1)); |
---|
916 | |
---|
917 | if ((indexes[0] > chunk.getPosition()) && // starts in between the delta |
---|
918 | (indexes[1] >= (chunk.getPosition() + chunk.size()))) // ends after the delta |
---|
919 | { |
---|
920 | result.add(lastPath.subPath(0, i + 1)); |
---|
921 | break; |
---|
922 | } |
---|
923 | } |
---|
924 | } |
---|
925 | } |
---|
926 | } |
---|
927 | |
---|
928 | /** |
---|
929 | * |
---|
930 | */ |
---|
931 | private void getPathsToParentTasksFullyInsideDelta(Delta delta, List<TaskPath> result) { |
---|
932 | getPathsToParentTasksFullyInsideChunk(delta.getOriginal(), leftHandSideTraversal, result); |
---|
933 | getPathsToParentTasksFullyInsideChunk(delta.getRevised(), rightHandSideTraversal, result); |
---|
934 | } |
---|
935 | |
---|
936 | /** |
---|
937 | * If a chunk has a minimum number of 2 entries and at least one of them belongs to an iteration |
---|
938 | * the does not cover the other and also expands to preceding or succeeding paths in the |
---|
939 | * traversal, we can no merge the situation. |
---|
940 | */ |
---|
941 | private void getPathsToParentTasksFullyInsideChunk(Chunk chunk, |
---|
942 | TaskTraversal traversal, |
---|
943 | List<TaskPath> result) |
---|
944 | { |
---|
945 | @SuppressWarnings("unchecked") |
---|
946 | List<TaskPath> paths = (List<TaskPath>) chunk.getLines(); |
---|
947 | |
---|
948 | TaskPath commonPath = getCommonPath(paths); |
---|
949 | |
---|
950 | for (TaskPath path : paths) { |
---|
951 | for (int i = commonPath.size(); i < path.size(); i++) { |
---|
952 | if (!(path.getLast() instanceof IEventTask)) { |
---|
953 | // check, if the parent task covers things fully inside the delta |
---|
954 | int[] indexes = traversal.getIndexesRootedBy(path.subPath(0, i + 1)); |
---|
955 | |
---|
956 | if ((chunk.getPosition() <= indexes[0]) && // starts in the delta |
---|
957 | (indexes[1] < (chunk.getPosition() + chunk.size()))) // ends in the delta |
---|
958 | { |
---|
959 | System.out.println("found path to parent task lieing completely in the " + |
---|
960 | "delta: " + path.subPath(0, i + 1)); |
---|
961 | result.add(path.subPath(0, i + 1)); |
---|
962 | break; |
---|
963 | } |
---|
964 | } |
---|
965 | } |
---|
966 | } |
---|
967 | } |
---|
968 | |
---|
969 | /** |
---|
970 | * |
---|
971 | */ |
---|
972 | @SuppressWarnings("unchecked") |
---|
973 | private int getDiffLevel(Patch patch, |
---|
974 | TaskTraversal traversal1, |
---|
975 | TaskTraversal traversal2) |
---|
976 | { |
---|
977 | int diffCount = 0; |
---|
978 | int allCount = 0; |
---|
979 | |
---|
980 | for (Delta delta : patch.getDeltas()) { |
---|
981 | for (TaskPath path : (List<TaskPath>) delta.getOriginal().getLines()) { |
---|
982 | // inefficient actions are counted later. |
---|
983 | if (!isInefficientAction(path.getLast())) { |
---|
984 | diffCount += getDiffPenalty(path.getLast()); |
---|
985 | } |
---|
986 | } |
---|
987 | for (TaskPath path : (List<TaskPath>) delta.getRevised().getLines()) { |
---|
988 | // inefficient actions are counted later. |
---|
989 | if (!isInefficientAction(path.getLast())) { |
---|
990 | diffCount += getDiffPenalty(path.getLast()); |
---|
991 | } |
---|
992 | } |
---|
993 | } |
---|
994 | |
---|
995 | //if (getPathsToRealIntersectingIterations().size() > 0) { |
---|
996 | // add a penalty if intersecting iterations are present |
---|
997 | //count += 10; |
---|
998 | //} |
---|
999 | |
---|
1000 | // add a penalty for inefficient actions which are equal as they have to be treated |
---|
1001 | // unequal, too. Otherwise, many scrolls, e.g., would pretend a high equality of tasks |
---|
1002 | // which contain only some non inefficient actions |
---|
1003 | for (TaskPath path : traversal1.getTraversalPaths()) { |
---|
1004 | if (isInefficientAction(path.getLast())) { |
---|
1005 | diffCount++; |
---|
1006 | } |
---|
1007 | else { |
---|
1008 | allCount += getDiffPenalty(path.getLast()); |
---|
1009 | } |
---|
1010 | } |
---|
1011 | |
---|
1012 | for (TaskPath path : traversal2.getTraversalPaths()) { |
---|
1013 | if (isInefficientAction(path.getLast())) { |
---|
1014 | diffCount++; |
---|
1015 | } |
---|
1016 | else { |
---|
1017 | allCount += getDiffPenalty(path.getLast()); |
---|
1018 | } |
---|
1019 | } |
---|
1020 | |
---|
1021 | return (100 * diffCount) / allCount; |
---|
1022 | } |
---|
1023 | |
---|
1024 | /** |
---|
1025 | * |
---|
1026 | */ |
---|
1027 | private boolean isInefficientAction(ITask task) { |
---|
1028 | ITaskInstance inst = task.getInstances().iterator().next(); |
---|
1029 | |
---|
1030 | if ((inst instanceof IEventTaskInstance) && |
---|
1031 | (((IEventTaskInstance) inst).getEvent().getType() instanceof Scroll)) |
---|
1032 | { |
---|
1033 | return true; |
---|
1034 | } |
---|
1035 | else if (task instanceof IMarkingTemporalRelationship) { |
---|
1036 | return isInefficientAction(((IMarkingTemporalRelationship) task).getMarkedTask()); |
---|
1037 | } |
---|
1038 | else { |
---|
1039 | return false; |
---|
1040 | } |
---|
1041 | } |
---|
1042 | |
---|
1043 | |
---|
1044 | /** |
---|
1045 | * |
---|
1046 | */ |
---|
1047 | private int getDiffPenalty(ITask task) { |
---|
1048 | if (task instanceof IEventTask) { |
---|
1049 | return 1; |
---|
1050 | } |
---|
1051 | else if (task instanceof IMarkingTemporalRelationship) { |
---|
1052 | return getDiffPenalty(((IMarkingTemporalRelationship) task).getMarkedTask()); |
---|
1053 | } |
---|
1054 | else if (task instanceof IStructuringTemporalRelationship) { |
---|
1055 | int result = 0; |
---|
1056 | |
---|
1057 | for (ITask child : ((IStructuringTemporalRelationship) task).getChildren()) { |
---|
1058 | result += getDiffPenalty(child); |
---|
1059 | } |
---|
1060 | |
---|
1061 | if (task instanceof ISelection) { |
---|
1062 | result /= ((IStructuringTemporalRelationship) task).getChildren().size(); |
---|
1063 | } |
---|
1064 | |
---|
1065 | return result; |
---|
1066 | } |
---|
1067 | |
---|
1068 | throw new IllegalArgumentException("unknown type of task: " + task); |
---|
1069 | } |
---|
1070 | } |
---|