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 cz.cuni.amis.pogamut.sposh.exceptions.InvalidNameException;
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   * <p/>
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   * <p/>
24   * <b>How does it work?</b> Fact: Engine remembers which node of the drive it
25   * processed last time. When engine evaluates the drive, it starts evaluation in
26   * the place, where it left off in the last evaluation of drive. Engine
27   * evaluates the plan each tick, but it doesn't matter if drive was evaluated in
28   * previsous tick or 10000 ticks ago (e.g. trigger of the drive was unsatisfied
29   * for a long time), engine will take the state (state=path from the drive to
30   * some node in the drive tree) it was left off last time and goes from there,
31   * e.g. if drive was evaluating which action it should do, it will continue to
32   * do so, if drive was executing some action, it will continue in its execution.
33   * <p/>
34   * By "will continue in..." is meant "will try to continue...", because in many
35   * cases, it will fail(the action will signal its failure ect.), but it doesn't
36   * matter, in such case the engine will traverse the drive's decision tree from
37   * the root. Originally it was supposed to enable quick switch to some action
38   * and go back to doing whatever the original drive was doing. Not sure about
39   * how useful it is.
40   *
41   * @see Competence Competences are similar, but slightly different. There is a
42   * lot of confusion about how they differ from DC, so take a look.
43   * @author HonzaH
44   */
45  public final class DriveCollection extends PoshDummyElement<DriveCollection, PoshPlan> implements INamedElement, IConditionElement<DriveCollection> {
46  
47      /**
48       * Name of the collection
49       */
50      private String name;
51      /**
52       * Goal of drive collection. If goal is not fulfilled, run the engine.
53       * Useful when there are multiple plans (e.g. one for shooting and one for
54       * moving), goal of the shooting plan would be "no-enemy-in-sight" so when
55       * enemy would be in sight, it would work, but w/o enemy, it would be
56       * dormant. In most plans, goal is "fail" so plan is evaluated indefinitely.
57       */
58      private final Trigger<DriveCollection> goal = new Trigger<DriveCollection>(this);
59      /**
60       * List of drive elements of this DC. Basically one choice at the top of
61       * decision tree what to do next.
62       */
63      private final List<DriveElement> elements = new LinkedList<DriveElement>();
64      /**
65       * Unmodifiable list of drive elements of this DC, basically a wrapper for {@link DriveCollection#elements}.
66       */
67      private final List<DriveElement> elementsUm = Collections.unmodifiableList(elements);
68      /**
69       * Property name for change of name.
70       */
71      public static final String dcName = "dcName";
72      /**
73       * Data flavor of DC, used for drag and drop
74       */
75      public static final DataFlavor dataFlavor = new DataFlavor(DriveCollection.class, "drive-collection-node");
76  
77      /**
78       * Create new drive collection without any trigger or drives, specify only
79       * name.
80       *
81       * @param name Name of the drive collection.
82       */
83      DriveCollection(String name) {
84          assert name != null && !name.isEmpty();
85          this.name = name;
86      }
87  
88      /**
89       * Add passed drive at the specified index of all drives.
90       */
91      /**
92       * Add passed drive as the last drive of this DC and emit new it.
93       *
94       * @param drive drive to add
95       */
96      public void addDrive(DriveElement drive) throws DuplicateNameException {
97          int behindLastDriveIndex = elementsUm.size();
98          addDrive(behindLastDriveIndex, drive);
99      }
100 
101     public void addDrive(int index, DriveElement drive) throws DuplicateNameException {
102         assert !elementsUm.contains(drive);
103         // sanity: check that new drive's parent doesn't have drive listed as a child
104         if (drive.getParent() != null) {
105             assert !drive.getParent().getChildDataNodes().contains(drive);
106         }
107 
108         if (isUsedName(drive.getName(), elements)) {
109             throw new DuplicateNameException("DC " + name + " already have drive with name " + drive.getName());
110         }
111 
112         elements.add(index, drive);
113         drive.setParent(this);
114 
115         emitChildNode(drive);
116     }
117 
118     /**
119      * Get goal of the DC. As long as goal is not satisfied, the DC will go on.
120      *
121      * @return goal of the DC
122      */
123     public Trigger<DriveCollection> getGoal() {
124         return goal;
125     }
126 
127     /**
128      * Get goal of the DC.
129      *
130      * @see #getGoal()
131      * @return Goal, never null.
132      */
133     @Override
134     public Trigger<DriveCollection> getCondition() {
135         return getGoal();
136     }
137 
138     /**
139      * Get list of all drives of this DC in correct order (drive with higest
140      * priority is first, drive with lowest priority is last).
141      *
142      * @return unmodifiable list of drives.
143      */
144     public List<DriveElement> getDrives() {
145         return elementsUm;
146     }
147 
148     /**
149      * Get drive with @id, equivalent of {@link #getDrives() }.{@link List#get(int)
150      * }.
151      *
152      * @return Drive with @id
153      */
154     public DriveElement getDrive(int id) {
155         return elementsUm.get(id);
156     }
157 
158     /**
159      * Serialize DC into a parser readable form. If goal is empty, don't include
160      * it. Example:
161      * <code>
162      *   (DC funbot (goal ((cz.cuni.HaveFun)(cz.cuni.Sing)))
163      *      (drives (
164      *                (fun (trigger ((cz.cuni.IsAtParty)(cz.cuni.FriendsInSight)) ) cz.cuni.Enjoy )
165      *                (default cz.cuni.DoNothing)
166      *              )
167      *      )
168      *   )
169      * </code>
170      *
171      * @return multi-line string that parser can read.
172      */
173     @Override
174     public String toString() {
175         StringBuilder sb = new StringBuilder();
176         sb.append("\t(DC ").append(name);
177 
178         if (!goal.isEmpty()) {
179             sb.append(" (goal ").append(goal.toString()).append(')');
180         }
181         sb.append('\n');
182 
183         sb.append("\t\t(drives \n");
184         for (DriveElement element : elements) {
185             // Keep the extra braces for compatibility
186             sb.append("\t\t\t  (").append(element.toString()).append(")\n");
187         }
188         sb.append("\t\t)\n");
189         sb.append("\t)");
190         return sb.toString();
191     }
192 
193     /**
194      * Get all child nodes of the DC. It consists of goal (at first place) and
195      * all drives of DC from second place forward (in correct order).
196      *
197      * @return all children of this DC,
198      */
199     @Override
200     public List<PoshElement> getChildDataNodes() {
201         List<PoshElement> children = new ArrayList<PoshElement>(goal);
202         children.addAll(elementsUm);
203 
204         return children;
205     }
206 
207     /**
208      * Get name of the DC
209      *
210      * @return name of the DC
211      */
212     @Override
213     public String getName() {
214         return name;
215     }
216 
217     /**
218      * Change name of the DC and notify property listeners.
219      *
220      * @param newName New name of the DC.
221      * @throws InvalidNameException throw if name is not valid (spaces,
222      * braces...)
223      */
224     public void setName(String newName) throws InvalidNameException {
225         newName = newName.trim();
226         if (newName.matches(IDENT_PATTERN)) {
227             String oldName = name;
228             name = newName;
229             firePropertyChange(dcName, oldName, name);
230         } else {
231             throw new InvalidNameException("Name " + newName + " is not valid.");
232         }
233     }
234 
235     /**
236      * Because DC is not referenced anywhere, it has same effect as {@link #setName(java.lang.String)
237      * }.
238      *
239      * @see #setName(java.lang.String)
240      */
241     @Override
242     public void rename(String newName) throws InvalidNameException, CycleException, DuplicateNameException {
243         setName(newName);
244     }
245 
246     @Override
247     public boolean moveChild(int newIndex, PoshElement child) {
248         assert child instanceof DriveElement;
249         return moveChildInList(elements, (DriveElement) child, newIndex);
250     }
251 
252     @Override
253     public DataFlavor getDataFlavor() {
254         return dataFlavor;
255     }
256 
257     @Override
258     public LapType getType() {
259         return LapType.DRIVE_COLLECTION;
260     }
261 
262     /**
263      * Remove drive from the drive collection. If it is the last drive of DC,
264      * create new one before removing passed drive in order to have at least one
265      * drive in DC at all times.
266      *
267      * @param drive Drive to be removed.
268      */
269     public void removeDrive(DriveElement drive) {
270         assert elements.contains(drive);
271 
272         if (elements.size() == 1) {
273             String unusedName = getUnusedName("drive-", elementsUm);
274             try {
275                 addDrive(LapElementsFactory.createDriveElement(unusedName));
276             } catch (DuplicateNameException ex) {
277                 String msg = "Unused name " + unusedName + " is not unused.";
278                 Logger.getLogger(DriveCollection.class.getName()).log(Level.SEVERE, msg, ex);
279                 throw new FubarException(msg, ex);
280             }
281         }
282 
283         int removedDrivePosition = elementsUm.indexOf(drive);
284 
285         elements.remove(drive);
286         drive.setParent(null);
287 
288         emitChildDeleted(drive, removedDrivePosition);
289     }
290 }