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 cz.cuni.amis.pogamut.sposh.exceptions.MissingParameterException;
8   import java.awt.datatransfer.DataFlavor;
9   import java.text.MessageFormat;
10  import java.util.ArrayList;
11  import java.util.Collections;
12  import java.util.LinkedList;
13  import java.util.List;
14  import java.util.logging.Level;
15  import java.util.logging.Logger;
16  
17  /**
18   * AP is a named sequence of {@link TriggeredAction actions}.
19   *
20   * Action patterns: or simple sequences. These are a basic kind of plan
21   * aggregate which turn out to be useful in quite a lot of situations, despite
22   * their lack of flexibility. They reduce the combinatorial complexity of the
23   * agent when a full competence is not really necessary.
24   *
25   * @author HonzaH
26   */
27  public final class ActionPattern extends PoshDummyElement<ActionPattern, PoshPlan> implements IParametrizedElement {
28  
29      /**
30       * Name of this AP.
31       */
32      private String name;
33      /**
34       * Comment about this pattern
35       *
36       * @deprecated Because editor doesn't support it
37       */
38      @Deprecated
39      private String comment;
40      /**
41       * List of all actions contained in this AP.
42       */
43      private final List<TriggeredAction> actions = new ArrayList<TriggeredAction>();
44      /**
45       * Unmodifiable list of actions, proxy for {@link ActionPattern#actions}.
46       */
47      private final List<TriggeredAction> actionsUm = Collections.unmodifiableList(actions);
48      /**
49       * Formal parameters of this AP. Can be passed to the actions.
50       */
51      protected FormalParameters params;
52      /**
53       * Property name for name of this AP.
54       */
55      public static final String apName = "apName";
56      /**
57       * Property name for comment
58       */
59      public static final String apComment = "apComment";
60      /**
61       * Property name for parameters of AP.
62       */
63      public static final String apParams = "apParams";
64      /**
65       * Data flavor of AP for drag and drop.
66       */
67      public static final DataFlavor dataFlavor = new DataFlavor(ActionPattern.class, "action-pattern-node");
68  
69      /**
70       * Create new AP.
71       *
72       * @param name Name of the AP
73       * @param params Formal parameters of the AP
74       * @param ap List of actions. AP will make a shallow copy
75       * @param comment Comment about this action pattern
76       */
77      ActionPattern(String name, FormalParameters params, List<TriggeredAction> ap, String comment) {
78          assert name != null;
79          assert params != null;
80          assert comment != null;
81  
82          this.name = name;
83          this.params = params;
84          this.comment = comment;
85  
86          for (TriggeredAction action : ap) {
87              assert action.getParent() == null;
88              action.setParent(this);
89              actions.add(action);
90          }
91      }
92  
93      /**
94       * Add new TriggeredAction as child of this AP.
95       *
96       * @param action action to be added into this AP
97       */
98      public void addAction(TriggeredAction action) throws CycleException {
99          int beyondLastActionIndex = actionsUm.size();
100         addAction(beyondLastActionIndex, action);
101     }
102 
103     /**
104      * Add new @action as action of this AP, emit new child.
105      *
106      * @param index Index at which to put the @action.
107      * @param action orphan
108      * @throws CycleException
109      */
110     public void addAction(int index, TriggeredAction action) throws CycleException {
111         assert !action.isChildOfParent();
112 
113         action.setParent(this);
114         actions.add(index, action);
115 
116         PoshPlan root = getRootNode();
117         if (root != null && root.isCycled()) {
118             actions.remove(action);
119             throw CycleException.createFromName(action.getName());
120         }
121         emitChildNode(action);
122     }
123 
124     @Override
125     public String toString() {
126         StringBuilder sb = new StringBuilder("\t(AP ");
127         sb.append(name);
128 
129         // parameters of the competence are right after declaration
130         if (!params.isEmpty()) {
131             sb.append(" vars(");
132             sb.append(params.toString());
133             sb.append(")");
134         }
135 
136         sb.append(" (");
137         boolean firstAction = true;
138         for (TriggeredAction action : actionsUm) {
139             if (!firstAction) {
140                 sb.append(' ');
141             } else {
142                 firstAction = false;
143             }
144             sb.append(action.toString());
145         }
146         sb.append(')');
147         if (!comment.isEmpty()) {
148             sb.append(" \"");
149             sb.append(comment);
150             sb.append('"');
151         }
152         sb.append(")\n");
153         return sb.toString();
154     }
155 
156     @Override
157     public List<TriggeredAction> getChildDataNodes() {
158         return actionsUm;
159     }
160 
161     /**
162      * Set name of AP. Check for cycles and make sure the name is unique not
163      * same as some AP or Competence.
164      *
165      * @param name string tham matches <tt>IDENT_PATTERN</tt>
166      */
167     public void setName(String name) throws InvalidNameException, DuplicateNameException, CycleException {
168         name = name.trim();
169 
170         if (!name.matches(IDENT_PATTERN)) {
171             throw InvalidNameException.create(name);
172         }
173         if (!getName().equals(name)) {
174             if (getRootNode() != null && !getRootNode().isUniqueNodeName(name)) {
175                 throw DuplicateNameException.create(name);
176             }
177         }
178 
179         String oldName = this.name;
180         this.name = name;
181 
182         if (getRootNode() != null && getRootNode().isCycled()) {
183             this.name = oldName;
184             throw CycleException.createFromName(name);
185         }
186 
187         firePropertyChange(apName, oldName, name);
188     }
189 
190     /**
191      * Get name of AP.
192      *
193      * @return Name of the AP
194      */
195     @Override
196     public String getName() {
197         return name;
198     }
199 
200     /**
201      * Get HTML description of AP.
202      * @return HTML description of node.
203      */
204     public String getHtmlDescription() {
205         return "<html>Action pattern: " + getName() + "<br/><pre>" + toString() + "</pre></html>";
206     }
207     
208     @Override
209     public boolean moveChild(int newIndex, PoshElement child) {
210         assert child instanceof TriggeredAction;
211         return moveChildInList(actions, (TriggeredAction) child, newIndex);
212     }
213 
214     @Override
215     public DataFlavor getDataFlavor() {
216         return dataFlavor;
217     }
218 
219     @Override
220     public LapType getType() {
221         return LapType.ACTION_PATTERN;
222     }
223 
224     /**
225      * Remove action from this AP and emit notification.
226      *
227      * @param action Action that will be removed.
228      */
229     public void removeAction(TriggeredAction action) {
230         assert actions.contains(action);
231 
232         if (actions.size() == 1) {
233             try {
234                 addAction(LapElementsFactory.createAction());
235             } catch (CycleException ex) {
236                 String msg = MessageFormat.format("Adding an action with default name {0} causes a cycle.", LapElementsFactory.DEFAULT_ACTION);
237                 Logger.getLogger(ActionPattern.class.getName()).log(Level.SEVERE, msg, ex);
238                 throw new FubarException(msg, ex);
239             }
240         }
241 
242         int removedActionPosition = actionsUm.indexOf(action);
243 
244         actions.remove(action);
245         action.setParent(null);
246 
247         emitChildDeleted(action, removedActionPosition);
248     }
249 
250     /**
251      * Get list of actions in this AP.
252      *
253      * @return unmodifiable list of actions.
254      */
255     public List<TriggeredAction> getActions() {
256         return actionsUm;
257     }
258 
259     /**
260      * Get formal parametrs of this AP. Formal paramaters contain map of
261      * parameterName-default value this AP accepts.
262      *
263      * @return formal parameters of this AP
264      */
265     @Override
266     public FormalParameters getParameters() {
267         return params;
268     }
269 
270     /**
271      * Get comment of the AP.
272      *
273      * @deprecated Because editor doesn't support it.
274      */
275     @Deprecated
276     String getComment() {
277         return comment;
278     }
279 
280     /**
281      * Set comment
282      *
283      * @deprecated Because editor doesn't support it.
284      */
285     @Deprecated
286     void setComment(String newComment) {
287         assert newComment != null;
288         String oldComment = this.comment;
289         this.comment = newComment;
290 
291         firePropertyChange(apComment, oldComment, newComment);
292     }
293 
294     /**
295      * Change parameters of the AP.
296      *
297      * @param newParams New parameters of AP.
298      */
299     @Override
300     public void setParameters(FormalParameters newParams) {
301         FormalParameters oldParams = params;
302         this.params = newParams;
303 
304         firePropertyChange(apParams, oldParams, newParams);
305     }
306 
307     // TODO: Unify with rename in competence
308     /**
309      * Rename {@link ActionPattern} in the {@link PoshPlan}.
310      *
311      * This method finds all references ín the plan where this AP is references
312      * and changes them to the @newAPName
313      *
314      * @param newAPName New name for this AP in the plan.
315      */
316     @Override
317     public void rename(String newAPName) throws InvalidNameException, DuplicateNameException, CycleException {
318         PoshPlan plan = getRootNode();
319         if (plan == null) {
320             throw new IllegalStateException("AP " + getName() + " is not part of a plan.");
321         }
322 
323         List<TriggeredAction> planReferences = plan.getAllReferences();
324         List<TriggeredAction> referencingActions = new LinkedList<TriggeredAction>();
325 
326         for (TriggeredAction planAction : planReferences) {
327             boolean actionReferencesAP = planAction.getName().equals(getName());
328             if (actionReferencesAP) {
329                 referencingActions.add(planAction);
330             }
331         }
332 
333         // TODO: rollback if some error happens?
334         for (TriggeredAction referencingAction : referencingActions) {
335             referencingAction.setActionName(newAPName);
336         }
337 
338         this.setName(newAPName);
339     }
340 
341     /**
342      * Get action with @actionId. Equivalent of {@link #getActions() }.{@link List#get(int)
343      * }.
344      *
345      * @param actionId Id of desired action.
346      * @return Specified action.
347      */
348     public TriggeredAction getAction(int actionId) {
349         return actionsUm.get(actionId);
350     }
351 }