View Javadoc

1   package cz.cuni.amis.pogamut.sposh.elements;
2   
3   import cz.cuni.amis.pogamut.sposh.exceptions.DuplicateNameException;
4   import cz.cuni.amis.pogamut.sposh.exceptions.FubarException;
5   import cz.cuni.amis.pogamut.sposh.exceptions.InvalidNameException;
6   import cz.cuni.amis.pogamut.sposh.exceptions.UnexpectedElementException;
7   import java.util.ArrayList;
8   import java.util.List;
9   import java.awt.datatransfer.DataFlavor;
10  import java.util.Collections;
11  import java.util.LinkedList;
12  import java.util.logging.Level;
13  import java.util.logging.Logger;
14  
15  /**
16   * This is root of POSH plan in execution sense. In source of POSH plan, this is
17   * leaf of root. Every POSH plan can have only one DriveCollection.
18   *
19   * drive collection: this is the root of the POSH hierarchy, and on every POSH
20   * action-selection cycle, this reconsiders which goal the agent should be
21   * working on. This is how a BOD agent can respond rapidly to changes in the
22   * environment.
23   *
24   * @author HonzaH
25   */
26  public final class DriveCollection extends NamedLapElement {
27  
28      /**
29       * Name of the collection
30       */
31      private String name;
32      /**
33       * Goal of drive collection. If goal is not fulfilled, run the engine.
34       * Useful when there are multiple plans (e.g. one for shooting and one for
35       * moving), goal of the shooting plan would be "no-enemy-in-sight" so when
36       * enemy would be in sight, it would work, but w/o enemy, it would be
37       * dormant.
38       */
39      private Goal goal;
40      /**
41       * List of drive elements of this DC. Basically one choice at the top of
42       * decision tree what to do next.
43       */
44      private final List<DriveElement> elements = new LinkedList<DriveElement>();
45      /**
46       * Unmodifiable list of drive elements of this DC, basically a wrapper for {@link DriveCollection#elements}.
47       */
48      private final List<DriveElement> elementsUm = Collections.unmodifiableList(elements);
49      /**
50       * Property name for change of name.
51       */
52      public static final String dcName = "dcName";
53      /**
54       * Data flavor of DC, used for drag and drop
55       */
56      public static final DataFlavor dataFlavor = new DataFlavor(DriveCollection.class, "drive-collection-node");
57  
58      /**
59       * Create a new drive collection.
60       *
61       * @param name Name of new DC
62       * @param goal Goal of new collection, not null
63       * @param drives List of drives
64       */
65      DriveCollection(String name, Goal goal, List<DriveElement> drives) {
66          assert goal != null;
67          assert goal.getParent() == null;
68          assert drives != null;
69  
70          this.name = name;
71          this.goal = goal;
72          this.goal.setParent(this);
73  
74          for (DriveElement element : drives) {
75              assert element.getParent() == null;
76  
77              elements.add(element);
78              element.setParent(this);
79          }
80      }
81  
82      /**
83       * Add passed drive as the last drive of this DC and emit new it.
84       *
85       * @param drive drive to add
86       */
87      public void addDrive(DriveElement drive) throws DuplicateNameException {
88          // sanity: check that new drive's parent doesn't have drive listed as a child
89          if (drive.getParent() != null) {
90              assert !drive.getParent().getChildDataNodes().contains(drive);
91          }
92  
93          if (isUsedName(drive.getName(), elements)) {
94              throw new DuplicateNameException("DC " + name + " already have drive with name " + drive.getName());
95          }
96  
97          elements.add(drive);
98          drive.setParent(this);
99  
100         emitChildNode(drive);
101     }
102 
103     /**
104      * Set goal of the drive collection. In nearly all collection, the goal is
105      * fail. Remove old goal, add and emit new children.
106      *
107      * @param goal new goal of the DC
108      */
109     public void setGoalNode(Goal goal) {
110         this.goal.remove();
111 
112         this.goal = goal;
113         goal.setParent(this);
114 
115         // Emit the goal and its children
116         emitChildNode(goal);
117     }
118 
119     /**
120      * Get goal of the DC
121      *
122      * @return goal of the DC
123      */
124     public Goal getGoal() {
125         return goal;
126     }
127 
128     /**
129      * Get list of all drives of this DC in correct order (drive with higest
130      * priority is first, drive with lowest priority is last).
131      *
132      * @return unmodifiable list of drives.
133      */
134     public List<DriveElement> getDrives() {
135         return elementsUm;
136     }
137 
138     /**
139      * Serialize DC into a parser readable form.
140      *
141      * @return multi-line string that parser can read.
142      */
143     @Override
144     public String toString() {
145         String res;
146         res = "\t(DC " + name + " " + goal.toString() + '\n';
147         res += "\t\t(drives \n";
148         for (DriveElement element : elements) {
149             // Keep the extra braces for compatibility
150             res += "\t\t\t  (" + element.toString() + ")\n";
151         }
152         res += "\t\t)\n";
153         res += "\t)";
154         return res;
155     }
156 
157     /**
158      * Get all child nodes of the DC. It consists of goal (at first place) and
159      * all drives of DC from second place forward (in correct order).
160      *
161      * @return all children of this DC,
162      */
163     @Override
164     public List<PoshElement> getChildDataNodes() {
165         List<PoshElement> children = new ArrayList<PoshElement>(elements);
166 
167         children.add(0, goal);
168 
169         return children;
170     }
171 
172     /**
173      * Get name of the DC
174      * @return name of the DC
175      */
176     public String getName() {
177         return name;
178     }
179 
180     /**
181      * Change name of the DC and notify property listeners.
182      *
183      * @param newName New name of the DC.
184      * @throws InvalidNameException throw if name is not valid (spaces,
185      * braces...)
186      */
187     public void setName(String newName) throws InvalidNameException {
188         newName = newName.trim();
189         if (newName.matches(IDENT_PATTERN)) {
190             String oldName = name;
191             name = newName;
192             firePropertyChange(dcName, oldName, name);
193         } else {
194             throw new InvalidNameException("Name " + newName + " is not valid.");
195         }
196     }
197 
198     @Override
199     public boolean moveChild(PoshElement child, int relativePosition) {
200         return moveNodeInList(elements, child, relativePosition);
201     }
202 
203     @Override
204     public DataFlavor getDataFlavor() {
205         return dataFlavor;
206     }
207 
208     @Override
209     public void addChildDataNode(PoshElement child) throws DuplicateNameException {
210         if (child instanceof Goal) {
211             setGoalNode((Goal) child);
212         } else if (child instanceof DriveElement) {
213             addDrive((DriveElement) child);
214         } else {
215             throw new UnexpectedElementException("Class " + child.getClass().getSimpleName() + " not expected.");
216         }
217     }
218 
219     @Override
220     public void neutralizeChild(PoshElement child) {
221         if (child == goal) {
222             setGoalNode(Goal.createFail());
223         } else if (elements.contains(child)) {
224             if (elements.size() == 1) {
225                 String unusedName = getUnusedName("drive-", elementsUm);
226                 try {
227                     addDrive(LapElementsFactory.createDriveElement(unusedName));
228                 } catch (DuplicateNameException ex) {
229                     String msg = "Unused name " + unusedName + " is not unused.";
230                     Logger.getLogger(DriveCollection.class.getName()).log(Level.SEVERE, msg, ex);
231                     throw new FubarException(msg, ex);
232                 }
233             }
234             elements.remove(child);
235             child.remove();
236         } else {
237             throw new UnexpectedElementException("Not expecting " + child.toString());
238         }
239     }
240 }