View Javadoc

1   package cz.cuni.amis.pogamut.sposh.elements;
2   
3   import cz.cuni.amis.pogamut.sposh.exceptions.CycleException;
4   import cz.cuni.amis.pogamut.sposh.exceptions.DuplicateNameException;
5   import cz.cuni.amis.pogamut.sposh.exceptions.FubarException;
6   import java.awt.datatransfer.DataFlavor;
7   import java.util.ArrayList;
8   import java.util.Collections;
9   import java.util.HashSet;
10  import java.util.LinkedList;
11  import java.util.List;
12  import java.util.Set;
13  
14  /**
15   * Root node of whole lap plan, contains {@link DriveCollection}, all {@link Competence}
16   * and {@link ActionPattern} + optional documentation.
17   *
18   * @see DriveCollection top level of the decision tree
19   * @see ActionPattern
20   * @see Competence
21   * @see TriggeredAction
22   * @author HonzaH
23   */
24  public final class PoshPlan extends PoshDummyElement<PoshPlan, PoshPlan> {
25  
26      /**
27       * Name of the plan, from docnode, optional.
28       */
29      private String name = "";
30      /**
31       * Author of the plan, optional.
32       */
33      private String author = "";
34      /**
35       * Information about plan, optional.
36       */
37      private String info = "";
38      /**
39       * Drive collection of the plan. The top level decision choices.
40       */
41      private final DriveCollection dc;
42      /**
43       * List of all action patterns in the plan.
44       */
45      private final List<ActionPattern> aps = new ArrayList<ActionPattern>();
46      private final List<ActionPattern> apsUm = Collections.unmodifiableList(aps);
47      /**
48       * List of all competences in the plan.
49       */
50      private final List<Competence> cs = new ArrayList<Competence>();
51      private final List<Competence> csUm = Collections.unmodifiableList(cs);    
52      /**
53       * List of all adopts in the plan.
54       */
55      private final List<Adopt> ads = new ArrayList<Adopt>();
56      private final List<Adopt> adsUm = Collections.unmodifiableList(ads);    
57      /**
58       * Data flavor of posh plan(for drag-and-drop), not used anywhere, but
59       * required by interface.
60       */
61      public static final DataFlavor dataFlavor = new DataFlavor(PoshPlan.class, "posh_tree_root");
62      /**
63       * Name of the property for {@link #name}.
64       */
65      public static final String PROP_NAME = "posh-plan-name";
66      /**
67       * Name of the property for {@link #author}.
68       */
69      public static final String PROP_AUTHOR = "posh-plan-author";
70      /**
71       * Name of the property for {@link #info}.
72       */
73      public static final String PROP_INFO = "posh-plan-info";
74  
75      /**
76       * Create new plan along with empty {@link DriveCollection} as its main 
77       * decision point.
78       * @param driveCollectionName Name of the {@link DriveCollection} of 
79       * the plan.
80       */
81      PoshPlan(String driveCollectionName) {
82          this.dc = LapElementsFactory.createDriveCollection(driveCollectionName);
83          this.dc.setParent(this);
84      }
85  
86      
87      private Set<String> getReachableActionNames() {
88          Set<String> reachableNames = new HashSet<String>();
89          
90          for (DriveElement drive : dc.getDrives()) {
91              String driveActionName = drive.getAction().getName();
92              if (isAP(driveActionName)) {
93                  ActionPattern actionPattern = getAP(driveActionName);
94                  reachableNames.addAll(getActionPatternActionNames(actionPattern));
95                  
96              } else if (isC(driveActionName)) {
97                  Competence competence = getC(driveActionName);
98                  reachableNames.addAll(getCompetenceActionNames(competence));
99              } else {
100                 reachableNames.add(driveActionName);
101             }
102         }
103         
104         return reachableNames;
105     }
106     
107     private Set<String> getCompetenceActionNames(Competence c) {
108         throw new UnsupportedOperationException();
109     }
110 
111     private Set<String> getActionPatternActionNames(ActionPattern ap) {
112         throw new UnsupportedOperationException();
113     }
114     
115     /**
116      * Get all action names in the plan, i.e. actions is DC, AP and C. Careful,
117      * if name of action is same as name of AP/C, it is expanded and thus is not
118      * a name of action, but only reference to the AP/C.
119      *
120      * @return Names of all actions in the plan.
121      */
122     public Set<String> getActionsNames() {
123         Set<String> actionNames = new HashSet<String>();
124         List<TriggeredAction> actions = getActions();
125         
126         for (TriggeredAction action : actions) {
127             actionNames.add(action.getName());
128         }
129         
130         return actionNames;
131     }
132     
133     /**
134      * Get all actions in this plan. It includes all actions in drives, 
135      * competence elements, action patterns and adopts.
136      * 
137      * NOTE: This adds all actions
138      * @return Set of all actions in the plan.
139      */
140     public List<TriggeredAction> getActions() {
141         List<TriggeredAction> references = getAllReferences();
142         List<TriggeredAction> actions = new LinkedList<TriggeredAction>();
143         
144         for (TriggeredAction reference : references) {
145             String referenceName = reference.getName();
146             if (!isAP(referenceName) && !isC(referenceName) && !isAD(referenceName)) {
147                 actions.add(reference);
148             }
149         }
150         return actions;
151     }
152 
153     public List<TriggeredAction> getAllReferences() {
154         List<TriggeredAction> references = new LinkedList<TriggeredAction>();
155         for (DriveElement drive : dc.getDrives()) {
156             references.add(drive.getAction());
157         }
158         for (Competence competence : csUm) {
159             for (CompetenceElement competenceElement : competence.getChildDataNodes()) {
160                 references.add(competenceElement.getAction());
161             }
162         }
163         for (ActionPattern actionPattern : apsUm) {
164             for (TriggeredAction action : actionPattern.getActions()) {
165                 references.add(action);
166             }
167         }
168         for (Adopt adopt : adsUm) {
169         	references.add(adopt.getAdoptedElement());
170         }
171         return references;
172     }
173     
174     /**
175      * Get all sense names in the plan, i.e. senses in DC goal, DE trigger, CE
176      * trigger.
177      *
178      * @return Names of all senses in all elements of the plan.
179      */
180     public Set<String> getSensesNames() {
181         Set<String> senseNames = new HashSet<String>();
182 
183         addTriggerSenseNames(dc.getGoal(), senseNames);
184 
185         for (DriveElement drive : dc.getDrives()) {
186             addTriggerSenseNames(drive.getTrigger(), senseNames);
187         }
188         for (Competence competence : csUm) {
189             for (CompetenceElement competenceElement : competence.getChildDataNodes()) {
190                 addTriggerSenseNames(competenceElement.getTrigger(), senseNames);
191             }
192         }
193         for (Adopt adopt : adsUm) {
194         	addTriggerSenseNames(adopt.getExitCondition(), senseNames);
195         }
196         
197         return senseNames;
198     }
199 
200     /**
201      * Add names of the senses in the trigger into passed set.
202      *
203      * @param trigger trigger containing the senses
204      * @param senseNames set into which to add names
205      */
206     private void addTriggerSenseNames(Trigger<?> trigger, Set<String> senseNames) {
207         for (Sense triggerSense : trigger) {
208             senseNames.add(triggerSense.getName());
209         }
210     }
211 
212     /**
213      * Does the plan contains {@link Competence} with specified name?
214      *
215      * @param name name we are checking
216      * @return true if C with name exists in the plane, false otherwise.
217      */
218     public boolean isC(String name) {
219         return getC(name) != null;
220     }
221 
222     /**
223      * Return competence from this plan with specified name.
224      *
225      * @param name name of searched C
226      * @return competence or null if such C doesn't exists
227      */
228     public Competence getC(String name) {
229         for (Competence c : getCompetences()) {
230             if (c.getName().equals(name)) {
231                 return c;
232             }
233         }
234         return null;
235     }
236 
237 
238     /**
239      * Get competence with id, equivalent of {@link #getCompetences() }.{@link List#get(int) }.
240      */
241     public Competence getCompetence(int id) {
242         return csUm.get(id);
243     }
244         
245     
246     /**
247      * Does this plan contain AP with specified name?
248      *
249      * @param name name of AP we are checking
250      * @return true if AP with name exists in the plane, false otherwise.
251      */
252     public boolean isAP(String name) {
253         return getAP(name) != null;
254     }
255 
256     /**
257      * Return action pattern from the plan with specified name.
258      *
259      * @param name name of searched AP
260      * @return action pattern or null if such AP doesn't exists
261      */
262     public ActionPattern getAP(String name) {
263         for (ActionPattern ap : getActionPatterns()) {
264             if (ap.getName().equals(name)) {
265                 return ap;
266             }
267         }
268         return null;
269     }
270 
271     /**
272      * Get action pattern with id, equivalent of {@link #getActionPatterns() }.{@link List#get(int) }.
273      */
274     public ActionPattern getActionPattern(int id) {
275         return apsUm.get(id);
276     }
277     
278     /**
279      * Does this plan contain AD with specified name?
280      *
281      * @param name name of AD we are checking
282      * @return true if AD with name exists in the plane, false otherwise.
283      */
284     public boolean isAD(String name) {
285     	return getAD(name) != null;
286     }
287     
288     /**
289      * Returns adopt from the plan with specified name.
290      *
291      * @param name name of searched AD
292      * @return adopt or null if such AD doesn't exists
293      */
294     public Adopt getAD(String name) {
295         for (Adopt ad : getAdopts()) {
296             if (ad.getName().equals(name)) {
297                 return ad;
298             }
299         }
300         return null;
301     }
302 
303     /**
304      * Get adopt with id, equivalent of {@link #getAdopts() }.{@link List#get(int) }.
305      */
306     public Adopt getAdopt(int id) {
307         return adsUm.get(id);
308     }
309     
310     
311     /**
312      * Check if passed string is different than names of all referencable nodes (competences, and
313      * action patterns and adopts). If it is, it can be used as name of new competence or
314      * action pattern.
315      *
316      * @param testedName name of tested string.
317      * @return true if it is unique, false otherwise.
318      */
319     public boolean isUniqueNodeName(String testedName) {
320         return !isAP(testedName) && !isC(testedName) && !isAD(testedName);
321     }
322 
323     /**
324      * Add competence node to the lap tree (add, emit)
325      *
326      * @param competenceNode
327      */
328     public void addCompetence(Competence competence) throws DuplicateNameException, CycleException {
329         if (!isUniqueNodeName(competence.getName())) {
330             throw DuplicateNameException.create(competence.getName());
331         }
332 
333         PoshPlan orgParent = competence.getParent();
334         cs.add(competence);
335         competence.setParent(this);
336 
337         if (isCycled()) {
338             competence.setParent(orgParent);
339             cs.remove(competence);
340 
341             throw CycleException.createFromName(competence.getName());
342         }
343 
344         emitChildNode(competence);
345     }
346     
347     public void addAdopt(Adopt adopt) throws DuplicateNameException, CycleException {
348     	if (!isUniqueNodeName(adopt.getName())) {
349             throw DuplicateNameException.create(adopt.getName());
350         }
351 
352         PoshPlan orgParent = adopt.getParent();
353         ads.add(adopt);
354         adopt.setParent(this);
355 
356         if (isCycled()) {
357         	adopt.setParent(orgParent);
358         	ads.remove(adopt);
359 
360             throw CycleException.createFromName(adopt.getName());
361         }
362 
363         emitChildNode(adopt);
364     }
365 
366     /**
367      * Get name of the plan. Name is the first string in <em>documentation</em>
368      * node.
369      *
370      * @return Name of the plan or empty string if name is not set.
371      */
372     public String getName() {
373         return name;
374     }
375 
376     /**
377      * Name of the plan, from docnode, optional.
378      *
379      * @param name the name to set.
380      */
381     public void setName(String name) {
382         assert name != null;
383         // XXX: check the input m#9
384         String oldName = this.name;
385         this.name = name;
386 
387         firePropertyChange(PROP_NAME, oldName, name);
388     }
389 
390     /**
391      * Get author of the plan, optional. Author is the second string in the 
392      * <em>documentation</em> node of the tree. 
393      *
394      * @return author of the plan or empty string if author is not set.
395      */
396     public String getAuthor() {
397         return this.author;
398     }
399 
400     /**
401      * Set new author of the plan.
402      *
403      * @param author new author or empty string for erasing.
404      */
405     public void setAuthor(String author) {
406         assert author != null;
407         // XXX: check inputs m#9
408         String oldAuthor = this.author;
409         this.author = author;
410 
411         firePropertyChange(PROP_AUTHOR, oldAuthor, author);
412     }
413 
414     /**
415      * Get info about this plan. Description of the plan is the third string of 
416      * the <em>documentation</em> node.
417      *
418      * @return informationa about this plan or empty string if info is not set.
419      */
420     public String getInfo() {
421         return this.info;
422     }
423 
424     /**
425      * Set informations about the plan. 
426      *
427      * @param info New info about plan or or empty string.
428      */
429     public void setInfo(String info) {
430         assert info != null;
431         // XXX: check inputs m#9
432         String oldInfo = this.info;
433         this.info = info;
434 
435         firePropertyChange(PROP_INFO, oldInfo, info);
436     }
437 
438     /**
439      * Get list of all competences.
440      *
441      * @return Unmodifiable list of all competences.
442      */
443     public List<Competence> getCompetences() {
444         return csUm;
445     }
446 
447     /**
448      * Add new AP to the lap plan (add, emit)
449      *
450      * @param actionPatternNode
451      */
452     public void addActionPattern(ActionPattern actionPattern) throws DuplicateNameException, CycleException {
453         if (!this.isUniqueNodeName(actionPattern.getName())) {
454             throw new DuplicateNameException("Action pattern '" + actionPattern.getName() + "' has duplicate name in POSH plan.");
455         }
456 
457         PoshPlan orgParent = actionPattern.getParent();
458         this.aps.add(actionPattern);
459         actionPattern.setParent(this);
460 
461         // test cycle
462         if (this.isCycled()) {
463             actionPattern.setParent(orgParent);
464             this.aps.remove(actionPattern);
465 
466             throw CycleException.createFromName(name);
467         }
468 
469         emitChildNode(actionPattern);
470     }
471 
472     /**
473      * Get list of all ADs in the plan.
474      *
475      * @return Unmodifiable list of all ADs in the plan.
476      */
477     public List<Adopt> getAdopts() {
478         return adsUm;
479     }
480     
481     /**
482      * Get list of all APs in the plan.
483      *
484      * @return Unmodifiable list of all APs in the plan.
485      */
486     public List<ActionPattern> getActionPatterns() {
487         return apsUm;
488     }
489 
490     /**
491      * Get drive collection of this plan.
492      */
493     public DriveCollection getDriveCollection() {
494         return dc;
495     }
496 
497     /**
498      * Is some element (AP/C) of the lap plan cycled? Doesn't even have to be
499      * attached to the drive.
500      *
501      * @return true if plan has a cycle, false otherwise
502      */
503     public boolean isCycled() {
504 
505         for (ActionPattern apNode : this.aps) {
506             if (findCycle(apNode, new HashSet<String>())) {
507                 return true;
508             }
509         }
510         for (Competence compNode : this.cs) {
511             if (findCycle(compNode, new HashSet<String>())) {
512                 return true;
513             }
514         }
515         return false;
516     }
517 
518     /**
519      * Try to find cycle from the AP subtree using DFS.
520      * <p/>
521      * The set contains actions on the the path from the root node to the parent
522      * of current node (apNode). If name of apNode is found in the set, we have
523      * a cycle (such name would be reference to some element along the path).
524      *
525      * @param apNode current node
526      * @param set set of all action names found from the root node to the parent
527      * of the apNode
528      * @return true if we have a cycle, false if not.
529      */
530     private boolean findCycle(ActionPattern apNode, Set<String> set) {
531         if (set.contains(apNode.getName())) {
532             return true;
533         }
534         set.add(apNode.getName());
535 
536         for (TriggeredAction action : apNode.getActions()) {
537             ActionPattern actionAP;
538             if ((actionAP = getAP(action.getName())) != null) {
539                 if (findCycle(actionAP, set)) {
540                     return true;
541                 }
542             }
543 
544             Competence actionComp;
545             if ((actionComp = getC(action.getName())) != null) {
546                 if (findCycle(actionComp, set)) {
547                     return true;
548                 }
549             }
550         }
551         set.remove(apNode.getName());
552         return false;
553     }
554 
555     /**
556      * Try to find cycle using DFS.
557      * <p/>
558      * The set contains actions on the the path from the root node to the parent
559      * of current node (compNode). If name of compNode is found in the set, we
560      * have a cycle (such name would be reference to some element along the
561      * path).
562      *
563      * @param compNode current node
564      * @param set set of all action names found from the root node to the parent
565      * of the compNode
566      * @return true if we have a cycle, false if not.
567      */
568     private boolean findCycle(Competence compNode, Set<String> set) {
569         if (set.contains(compNode.getName())) {
570             return true;
571         }
572 
573         set.add(compNode.getName());
574 
575         for (CompetenceElement element : compNode.getChildDataNodes()) {
576             TriggeredAction action = element.getAction();
577 
578             ActionPattern actionAP;
579             if ((actionAP = getAP(action.getName())) != null) {
580                 if (findCycle(actionAP, set)) {
581                     return true;
582                 }
583             }
584 
585             Competence actionComp;
586             if ((actionComp = getC(action.getName())) != null) {
587                 if (findCycle(actionComp, set)) {
588                     return true;
589                 }
590             }
591         }
592         set.remove(compNode.getName());
593         return false;
594     }
595 
596     /**
597      * Return serializaton of lap tree.
598      */
599     @Override
600     public String toString() {
601         StringBuilder sb = new StringBuilder("(");
602 
603         if (!name.isEmpty() || !author.isEmpty() || !info.isEmpty()) {
604             sb.append("\n\t(documentation \"");
605             sb.append(name.replace("\"", ""));
606             sb.append("\" \"");
607             sb.append(author.replace("\"", ""));
608             sb.append("\" \"");
609             sb.append(info.replace("\"", ""));
610             sb.append("\")\n");
611         }
612         for (Competence node : cs) {
613             sb.append('\n');
614             sb.append(node.toString());
615         }
616         for (ActionPattern node : this.aps) {
617             sb.append('\n');
618             sb.append(node.toString());
619         }
620         sb.append('\n');
621         sb.append(dc.toString());
622         sb.append("\n)");
623 
624         return sb.toString();
625     }
626 
627     @Override
628     public List<PoshElement> getChildDataNodes() {
629         List<PoshElement> children = new ArrayList<PoshElement>();
630 
631         children.addAll(cs);
632         children.addAll(aps);
633         children.addAll(ads);
634         children.add(dc);
635 
636         return children;
637     }
638 
639     @Override
640     public boolean moveChild(int newIndex, PoshElement child) {
641         throw new UnsupportedOperationException();
642 /*        if (cs.contains(child)) {
643             return moveNodeInList(cs, child, relativePosition);
644         }
645         if (aps.contains(child)) {
646             return moveNodeInList(aps, child, relativePosition);
647         }
648         return false;*/
649     }
650 
651     @Override
652     public DataFlavor getDataFlavor() {
653         return dataFlavor;
654     }
655 
656     @Override
657     public LapType getType() {
658         return LapType.PLAN;
659     }
660     
661     /**
662      * Remove competence from the plan and notify listeners about removal of
663      * child.
664      *
665      * @param removeCompetence Competence to be removed
666      */
667     public void removeCompetence(Competence removeCompetence) {
668         assert csUm.contains(removeCompetence);
669 
670         int removedCIndex = csUm.indexOf(removeCompetence);
671         
672         cs.remove(removeCompetence);
673         removeCompetence.setParent(null);
674 
675         emitChildDeleted(removeCompetence, removedCIndex);
676     }
677 
678     /**
679      * Remove action pattern from the plan and notify listeners of plan about
680      * removal of a child.
681      *
682      * @param ap Action pattern to be removed.
683      */
684     public void removeActionPattern(ActionPattern ap) {
685         assert apsUm.contains(ap);
686 
687         int removedAPIndex = apsUm.indexOf(ap);
688         
689         aps.remove(ap);
690         ap.setParent(null);
691 
692         emitChildDeleted(ap, removedAPIndex);
693     }
694 
695     /**
696      * Synchronize the lap tree to other the lap tree. After all is said and
697      * done, we should have two trees that would serialize into a same plan, but
698      * don't share any data.
699      *
700      * @param other The tree we are supposed to synchronize to.
701      */
702     public void synchronize(PoshPlan other) {
703         // First remove everything, combination of two plans could cause cycles)
704         ActionPattern[] apArray = aps.toArray(new ActionPattern[aps.size()]);
705         for (ActionPattern ap : apArray) {
706             removeActionPattern(ap);
707         }
708         Competence[] compArray = cs.toArray(new Competence[cs.size()]);
709         for (Competence c : compArray) {
710             removeCompetence(c);
711         }
712         Sense[] goalSenses = dc.getGoal().toArray(new Sense[0]);
713         for (Sense goalSense : goalSenses) {
714             dc.getGoal().remove(goalSense);
715         }
716 
717 
718         // Synchronzie from the other plan
719         try {
720             // because we can't remove all drives (that would be invalid tree, so 
721             // the dummy drive is always added), first we add extra drive with name 
722             // that isn't used in this or other drives. Action name is left alone, because
723             // there is no AP/C and the sync drive will be removed.
724             DriveElement[] originalDrives = dc.getDrives().toArray(new DriveElement[0]);
725             List<DriveElement> allDrives = new ArrayList<DriveElement>();
726             allDrives.addAll(dc.getDrives());
727             allDrives.addAll(other.dc.getDrives());
728             
729             String syncDriveName = getUnusedName("temporaryDriveSyncName", allDrives);
730             DriveElement syncDrive = LapElementsFactory.createDriveElementNoTriggers(syncDriveName);
731             dc.addDrive(syncDrive);
732             
733             for (DriveElement drive : originalDrives) {
734                 dc.removeDrive(drive);
735             }
736 
737             
738             for (DriveElement drive : other.dc.getDrives()) {
739                 dc.addDrive(LapElementsFactory.createDriveElement(drive));
740             }
741             
742             dc.removeDrive(syncDrive);
743             
744             for (ActionPattern ap : other.apsUm) {
745                 addActionPattern(LapElementsFactory.createActionPattern(ap));
746             }
747             for (Competence c : other.csUm) {
748                 addCompetence(LapElementsFactory.createCompetence(c));
749             }
750             for (Sense oGoalSense : other.dc.getGoal()) {
751                 dc.getGoal().add(LapElementsFactory.createSense(oGoalSense));
752             }
753         } catch (DuplicateNameException ex) {
754             throw new FubarException("Original tree should be correct thus new one should be too.", ex);
755         } catch (CycleException ex) {
756             throw new FubarException("Original tree should be correct thus new one should be too.", ex);
757         }
758     }
759 
760     /**
761      * Get id of adopt. The is is an index into all adopts. 
762      * @param adopt Adopt for which we want index
763      * @return found index
764      * @throws IllegalArgumentException If adopt is not in the adopts of the
765      * plan.
766      */
767     public int getAdoptId(Adopt adopt) {
768         return getElementId(adsUm, adopt);
769     }
770     
771     /**
772      * Get id of AP. The is is an index into all APs in the plan. 
773      * @param actionPattern AP for which we want index
774      * @return found index
775      * @throws IllegalArgumentException If AP is not in APs of the plan.
776      */
777     public int getActionPatternId(ActionPattern actionPattern) {
778         return getElementId(apsUm, actionPattern);
779     }
780 
781     /**
782      * Get id of competence. The is is an index into all Cs in the plan. 
783      * @param competence C for which we want index
784      * @return found index
785      * @throws IllegalArgumentException If competence is not in the competences
786      * of the plan.
787      */
788     public int getCompetenceId(Competence competence) {
789         return getElementId(csUm, competence);
790     }
791     
792 }