1 package cz.cuni.amis.pogamut.sposh.elements;
2
3 import java.text.CharacterIterator;
4 import java.text.StringCharacterIterator;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collection;
8 import java.util.Iterator;
9 import java.util.LinkedList;
10 import java.util.List;
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 public final class LapPath implements Iterable<LapPath.Link> {
41
42
43
44
45 public static final LapPath EMPTY = new LapPath();
46
47
48
49 public static LapPath PLAN_PATH = new LapPath().concat(LapType.PLAN, 0);
50
51
52
53
54 public static LapPath DRIVE_COLLECTION_PATH = new LapPath().concat(LapType.PLAN, 0).concat(LapType.DRIVE_COLLECTION, 0);
55
56
57
58 private static char LINK_SEPARATOR = '/';
59
60
61
62 private static char TYPE_SEPARATOR = ':';
63
64
65
66 private final Link[] links;
67
68 public LapPath() {
69 this.links = new Link[0];
70 }
71
72 LapPath(List<Link> links) {
73 this.links = links.toArray(new Link[links.size()]);
74 }
75
76
77
78
79
80 private void traverseRoot(Iterator<Link> iter) {
81 if (!iter.hasNext()) {
82 throw new IllegalStateException("No plan specified in the path.");
83 }
84 Link rootLink = iter.next();
85 if (rootLink.type != LapType.PLAN) {
86 throw new IllegalStateException("First link always has type " + LapType.PLAN + ", but is " + rootLink.type);
87 }
88 if (rootLink.id != 0) {
89 throw new IndexOutOfBoundsException("Id of plan must be 0, but is " + rootLink.id);
90 }
91 }
92
93
94
95
96
97 private void traversePlan(Iterator<Link> iter) {
98 Link planLink = iter.next();
99 if (planLink.type != LapType.DRIVE_COLLECTION) {
100 throw new IllegalStateException("Second link always has type " + LapType.DRIVE_COLLECTION + ", but is " + planLink.type);
101 }
102 if (planLink.id != 0) {
103 throw new IndexOutOfBoundsException("Id of drive collection must be 0, but is " + planLink.id);
104 }
105 }
106
107
108
109
110
111 private static PoshElement traverseDriveCollection(Iterator<Link> iter, PoshPlan plan) {
112 Link driveCollectionLink = iter.next();
113 DriveCollection driveCollection = plan.getDriveCollection();
114
115 if (driveCollectionLink.type == LapType.DRIVE_ELEMENT) {
116 DriveElement drive = driveCollection.getDrives().get(driveCollectionLink.id);
117 if (!iter.hasNext()) {
118 return drive;
119 }
120 return traverseDrive(iter, plan, drive);
121 } else if (driveCollectionLink.type == LapType.SENSE) {
122 Sense goalSense = driveCollection.getGoal().get(driveCollectionLink.id);
123 if (iter.hasNext()) {
124 throw new IllegalStateException("Goal sense must be last link, but isn't.");
125 }
126 return goalSense;
127 } else {
128 throw new IllegalStateException("Link was expected to be DRIVE or SENSE, but is " + driveCollectionLink.type);
129 }
130 }
131
132
133
134
135
136 private static PoshElement traverseDrive(Iterator<Link> iter, PoshPlan plan, DriveElement drive) {
137 Link driveLink = iter.next();
138
139 if (driveLink.type == LapType.SENSE) {
140 Sense driveTriggerSense = drive.getTrigger().get(driveLink.id);
141 if (iter.hasNext()) {
142 throw new IllegalStateException("Drive trigger sense must be last link, but isn't.");
143 }
144 return driveTriggerSense;
145 } else if (driveLink.type == LapType.ACTION) {
146 if (driveLink.id != 0) {
147 throw new IndexOutOfBoundsException("Id of action in drive should be 0, but is " + driveLink.id);
148 }
149 TriggeredAction action = drive.getAction();
150 if (!iter.hasNext()) {
151 return action;
152 }
153 return traverseReference(iter, plan, action);
154 } else {
155 throw new IllegalStateException("Link was expected to be SENSE or ACTION, but is " + driveLink.type);
156 }
157 }
158
159
160
161
162
163
164 private static PoshElement traverseReference(Iterator<Link> iter, PoshPlan plan, TriggeredAction reference) {
165 Link referenceLink = iter.next();
166 if (referenceLink.type == LapType.ADOPT) {
167 Adopt adopt = plan.getAdopts().get(referenceLink.id);
168 if (!reference.getName().equals(adopt.getName())) {
169 throw new IllegalStateException("Reference link with name " + reference.getName() + " does not match referencing Adopt " + referenceLink.id + " with name " + adopt.getName());
170 }
171 if (!iter.hasNext()) {
172 return adopt;
173 }
174 return traverseAdopt(iter, plan, adopt);
175 } else if (referenceLink.type == LapType.ACTION_PATTERN) {
176 ActionPattern actionPattern = plan.getActionPatterns().get(referenceLink.id);
177 if (!reference.getName().equals(actionPattern.getName())) {
178 throw new IllegalStateException("Reference link with name " + reference.getName() + " does not match referencing AP " + referenceLink.id + " with name " + actionPattern.getName());
179 }
180 if (!iter.hasNext()) {
181 return actionPattern;
182 }
183 return traverseActionPattern(iter, plan, actionPattern);
184 } else if (referenceLink.type == LapType.COMPETENCE) {
185 Competence competence = plan.getCompetences().get(referenceLink.id);
186 if (!reference.getName().equals(competence.getName())) {
187 throw new IllegalStateException("Reference link with name " + reference.getName() + " does not match referencing C " + referenceLink.id + " with name " + competence.getName());
188 }
189 if (!iter.hasNext()) {
190 return competence;
191 }
192 return traverseCompetence(iter, plan, competence);
193 } else {
194 throw new IllegalStateException("Link was expected to be ADOPT, ACTION_PATTERN or COMPETENCE, but is " + referenceLink.type);
195 }
196 }
197
198
199
200
201
202 private static PoshElement traverseAdopt(Iterator<Link> iter, PoshPlan plan, Adopt adopt) {
203 Link adoptLink = iter.next();
204 if (adoptLink.type == LapType.SENSE) {
205 Sense exitConditionSense = adopt.getExitCondition().get(adoptLink.id);
206 if (iter.hasNext()) {
207 throw new IllegalStateException("Exit condition sense of adopt is not last link in path.");
208 }
209 return exitConditionSense;
210 } else if (adoptLink.type == LapType.ACTION) {
211 if (adoptLink.id != 0) {
212 throw new IllegalStateException("Id of action in adopt should be 0, but is " + adoptLink.id);
213 }
214 TriggeredAction action = adopt.getAdoptedElement();
215 if (!iter.hasNext()) {
216 return action;
217 }
218 return traverseReference(iter, plan, action);
219 } else {
220 throw new IllegalStateException("Expecting sense or action in adopt, got " + adoptLink.type);
221 }
222 }
223
224
225
226
227
228 private static PoshElement traverseActionPattern(Iterator<Link> iter, PoshPlan plan, ActionPattern actionPattern) {
229 Link actionPatternLink = iter.next();
230 if (actionPatternLink.type != LapType.ACTION) {
231 throw new IllegalStateException("Action pattern can have only action subnodes.");
232 }
233
234 TriggeredAction action = actionPattern.getActions().get(actionPatternLink.id);
235 if (!iter.hasNext()) {
236 return action;
237 }
238 return traverseReference(iter, plan, action);
239 }
240
241
242
243
244
245 private static PoshElement traverseCompetence(Iterator<Link> iter, PoshPlan plan, Competence competence) {
246 Link competenceLink = iter.next();
247 if (competenceLink.type != LapType.COMPETENCE_ELEMENT) {
248 throw new IllegalStateException("Link from competence can be only choice, but is " + competenceLink.type);
249 }
250 CompetenceElement choice = competence.getChildDataNodes().get(competenceLink.id);
251 if (!iter.hasNext()) {
252 return choice;
253 }
254 return traverseChoice(iter, plan, choice);
255 }
256
257
258
259
260
261 private static PoshElement traverseChoice(Iterator<Link> iter, PoshPlan plan, CompetenceElement choice) {
262 Link choiceLink = iter.next();
263 if (choiceLink.type == LapType.SENSE) {
264 Sense choiceTriggerSense = choice.getTrigger().get(choiceLink.id);
265 if (iter.hasNext()) {
266 throw new IllegalStateException("Trigger sense of choice is not last link in path.");
267 }
268 return choiceTriggerSense;
269 } else if (choiceLink.type == LapType.ACTION) {
270 if (choiceLink.id != 0) {
271 throw new IllegalStateException("Id of action in choice should be 0, but is " + choiceLink.id);
272 }
273 TriggeredAction action = choice.getAction();
274 if (!iter.hasNext()) {
275 return action;
276 }
277 return traverseReference(iter, plan, action);
278 } else {
279 throw new IllegalStateException("Expecting sense or action in choice, got " + choiceLink.type);
280 }
281 }
282
283
284
285
286
287
288
289
290
291
292
293
294
295 public <T extends PoshElement> T traversePath(PoshPlan plan) {
296 Iterator<Link> iterator = Arrays.asList(links).iterator();
297
298 traverseRoot(iterator);
299 if (!iterator.hasNext()) {
300 return (T) plan;
301 }
302
303 traversePlan(iterator);
304 if (!iterator.hasNext()) {
305 return (T) plan.getDriveCollection();
306 }
307
308 return (T) traverseDriveCollection(iterator, plan);
309 }
310
311
312
313
314
315
316
317
318
319
320
321 public static LapPath getLinkPath(PoshElement endElement) {
322 LapPath path = LapPath.EMPTY;
323 PoshElement element = endElement;
324 PoshPlan plan = endElement.getRootNode();
325 while (element != plan) {
326 path = new LapPath().concat(element.getType(), element.getId()).concat(path);
327 element = element.getParent();
328 }
329 return path;
330 }
331
332
333
334
335
336
337
338 private static String getStringUntil(CharacterIterator iter, char terminator) {
339 StringBuilder sb = new StringBuilder();
340 for (char currentChar = iter.current();
341 currentChar != CharacterIterator.DONE && currentChar != terminator;
342 currentChar = iter.next()) {
343 sb.append(currentChar);
344 }
345 return sb.toString();
346 }
347
348 private static String getDecimals(CharacterIterator iter) {
349 StringBuilder sb = new StringBuilder();
350 for (char currentChar = iter.current();
351 currentChar >= '0' && currentChar <= '9';
352 currentChar = iter.next()) {
353 sb.append(currentChar);
354 }
355 return sb.toString();
356 }
357
358
359
360
361
362
363
364
365 private static Link parseLink(CharacterIterator iter) throws ParseException {
366
367 char linkSeparator = iter.current();
368 if (linkSeparator == CharacterIterator.DONE || linkSeparator != LapPath.LINK_SEPARATOR) {
369 throw new ParseException("Expected " + LapPath.LINK_SEPARATOR + " at " + iter.getIndex());
370 }
371 iter.next();
372 String typeString = getStringUntil(iter, LapPath.TYPE_SEPARATOR);
373
374 LapType linkType = null;
375 for (LapType type : LapType.values()) {
376 if (type.getName().equals(typeString)) {
377 linkType = type;
378 }
379 }
380 if (linkType == null) {
381 throw new ParseException("No LapType '" + typeString + "' exists.");
382 }
383
384
385 char typeSeparatorChar = iter.current();
386 if (typeSeparatorChar == CharacterIterator.DONE || typeSeparatorChar != LapPath.TYPE_SEPARATOR) {
387 throw new ParseException("Expected " + LapPath.TYPE_SEPARATOR + " at " + iter.getIndex());
388 }
389 iter.next();
390
391 try {
392 String idString = getDecimals(iter);
393 int linkId = Integer.parseInt(idString);
394
395 return new Link(linkType, linkId);
396 } catch (NumberFormatException ex) {
397 throw new ParseException(ex.getMessage());
398 }
399 }
400
401
402
403
404
405
406
407
408 public static LapPath parse(String serializedPath) throws ParseException {
409 CharacterIterator iter = new StringCharacterIterator(serializedPath);
410
411 List<Link> parsedLinks = new LinkedList<Link>();
412 do {
413 parsedLinks.add(parseLink(iter));
414 } while (iter.current() != CharacterIterator.DONE);
415 return new LapPath(parsedLinks);
416 }
417
418
419
420
421
422
423
424 public LapPath concat(LapType type, int id) {
425 List<Link> newPathLinks = new LinkedList<Link>(Arrays.asList(links));
426 newPathLinks.add(new Link(type, id));
427
428 return new LapPath(newPathLinks);
429 }
430
431 @Override
432 public String toString() {
433 StringBuilder sb = new StringBuilder();
434 for (Link link : links) {
435 sb.append(link.toString());
436 }
437 return sb.toString();
438 }
439
440 @Override
441 public boolean equals(Object obj) {
442 if (obj == null) {
443 return false;
444 }
445 if (getClass() != obj.getClass()) {
446 return false;
447 }
448 final LapPath other = (LapPath) obj;
449 if (!Arrays.deepEquals(this.links, other.links)) {
450 return false;
451 }
452 return true;
453 }
454
455 @Override
456 public int hashCode() {
457 int hash = 5;
458 hash = 59 * hash + Arrays.deepHashCode(this.links);
459 return hash;
460 }
461
462
463
464
465
466
467 public LapPath concat(LapPath appendPath) {
468 List<Link> concatLinks = new LinkedList<Link>(Arrays.asList(this.links));
469 concatLinks.addAll(Arrays.asList(appendPath.links));
470
471 LapPath concatPath = new LapPath(concatLinks);
472 return concatPath;
473 }
474
475
476
477
478 public LapPath concat(Link appendedLink) {
479 List<Link> concatLinks = new LinkedList<Link>(Arrays.asList(this.links));
480 concatLinks.add(appendedLink);
481
482 LapPath concatPath = new LapPath(concatLinks);
483 return concatPath;
484 }
485
486
487
488
489
490
491
492
493
494
495 public LapPath subpath(int beginIndex, int endIndex) {
496 if (beginIndex < 0) {
497 throw new IndexOutOfBoundsException("beginIndex(" + beginIndex + ") is negative.");
498 }
499 if (endIndex > links.length) {
500 throw new IndexOutOfBoundsException("endIndex(" + endIndex + ") is greater than length of path(" + links.length + ").");
501 }
502 if (beginIndex > endIndex) {
503 throw new IndexOutOfBoundsException("beginIndex(" + beginIndex + ") is greater than endIndex(" + endIndex + ").");
504 }
505 Link[] subarray = Arrays.copyOfRange(links, beginIndex, endIndex);
506 return new LapPath(Arrays.asList(subarray));
507 }
508
509
510
511
512 public int length() {
513 return links.length;
514 }
515
516 @Override
517 public Iterator<Link> iterator() {
518 return Arrays.asList(links).iterator();
519 }
520
521 public Link getLink(int linkId) {
522 return links[linkId];
523 }
524
525
526
527
528
529
530
531 int getLinkIndex(Link searchedLink) {
532 for (int index = 0; index < links.length; ++index) {
533 if (links[index] == searchedLink) {
534 return index;
535 }
536 }
537 throw new IllegalArgumentException("Unable to find the link.");
538 }
539
540
541
542
543 public static class Link {
544
545
546
547
548 private final LapType type;
549
550
551
552
553 private final int id;
554
555 public Link(LapType type, int id) {
556 this.type = type;
557 this.id = id;
558 }
559
560
561
562
563 public LapType getType() {
564 return type;
565 }
566
567
568
569
570
571 public int getId() {
572 return id;
573 }
574
575 @Override
576 public String toString() {
577 StringBuilder sb = new StringBuilder();
578
579 sb.append(LINK_SEPARATOR);
580 sb.append(type.getName());
581 sb.append(TYPE_SEPARATOR);
582 sb.append(id);
583
584 return sb.toString();
585 }
586
587 @Override
588 public boolean equals(Object obj) {
589 if (obj == null) {
590 return false;
591 }
592 if (getClass() != obj.getClass()) {
593 return false;
594 }
595 final Link other = (Link) obj;
596 if (this.type != other.type) {
597 return false;
598 }
599 if (this.id != other.id) {
600 return false;
601 }
602 return true;
603 }
604
605 @Override
606 public int hashCode() {
607 int hash = 3;
608 hash = 37 * hash + (this.type != null ? this.type.hashCode() : 0);
609 hash = 37 * hash + this.id;
610 return hash;
611 }
612 }
613 }