View Javadoc

1   package cz.cuni.amis.pogamut.sposh.elements;
2   
3   import cz.cuni.amis.pogamut.sposh.engine.PoshEngine;
4   import cz.cuni.amis.pogamut.sposh.exceptions.CycleException;
5   import cz.cuni.amis.pogamut.sposh.exceptions.DuplicateNameException;
6   import cz.cuni.amis.pogamut.sposh.exceptions.InvalidFormatException;
7   import cz.cuni.amis.pogamut.sposh.exceptions.InvalidNameException;
8   import cz.cuni.amis.pogamut.sposh.executor.IWorkExecutor;
9   import java.awt.datatransfer.DataFlavor;
10  import java.io.StringReader;
11  import java.util.*;
12  
13  /**
14   * Sense is a condition consisting from primitive call, predicate and value.
15   *
16   * When {@link PoshEngine} askes if sense is true, the sense will call the
17   * primitive through {@link IWorkExecutor} and compares returned value by
18   * predicate. If no argument was passed, use true, if no predicate was passed,
19   * use ==, e.g.
20   * <code>ammo</code> is equivalent of
21   * <code>ammo true ==</code>,
22   * <code>VisiblePlayerCount 4</code> is equivalent of
23   * <code>VisiblePlayerCount 4 ==</code>.
24   *
25   * @author Honza
26   */
27  public class Sense extends PoshDummyElement implements Comparable<Sense>, IReferenceElement, INamedElement {
28  
29      /**
30       * Various predicates used in <tt>Sense</tt>
31       *
32       * @author Honza
33       */
34      public enum Predicate {
35  
36          EQUAL(new String[]{"==", "="}),
37          NOT_EQUAL("!="),
38          LOWER("<"),
39          GREATER(">"),
40          LOWER_OR_EQUAL("<="),
41          GREATER_OR_EQUAL(">="),
42          DEFAULT("==");				// default is "=="
43          private String[] stringForm = null;
44  
45          private Predicate(String form) {
46              stringForm = new String[]{form};
47          }
48  
49          private Predicate(String[] form) {
50              stringForm = form;
51          }
52  
53          /**
54           * Get Predicate enum from passed string
55           *
56           * @param str passed predicate in string form
57           * @return Predicate
58           */
59          public static Predicate getPredicate(String str) {
60              if (str == null) {
61                  return Predicate.DEFAULT;
62              }
63  
64              str = str.trim();
65  
66              for (Predicate p : Predicate.values()) {
67                  for (String form : p.stringForm) {
68                      if (form.equals(str)) {
69                          return p;
70                      }
71                  }
72              }
73              throw new IllegalArgumentException("String \"" + str + "\" is not a predicate.");
74          }
75  
76          /**
77           * Get integer id of predicate. Used because of stupid combo box
78           * property pattern in NB.
79           *
80           * @return
81           */
82          public int getId() {
83              for (int i = 0; i < Predicate.values().length; i++) {
84                  if (values()[i] == this) {
85                      return i;
86                  }
87              }
88              throw new RuntimeException("Predicate \"" + this.toString() + "\" wasn't found in list of predicates.");
89          }
90  
91          @Override
92          public String toString() {
93              return stringForm[0];
94          }
95  
96          /**
97           * Get all predicates, without {@link #DEFAULT}.
98           */
99          public static Predicate[] getPredicates() {
100             List<Predicate> predicates = new LinkedList<Predicate>();
101             for (Predicate predicate : values()) {
102                 if (predicate != Predicate.DEFAULT) {
103                     predicates.add(predicate);
104                 }
105             }
106             return predicates.toArray(new Predicate[predicates.size()]);
107         }
108     }
109     /**
110      * Name of primitive that will be called along with list of parameters
111      */
112     private PrimitiveCall senseCall;
113     /**
114      * When evaluating result of sense, should I compare value returned by the
115      * primitive or should I just evaluate primtive and return?
116      */
117     private boolean compare = true;
118     /**
119      * Comparator used for comparison of value returned by primitive and operand
120      */
121     private Predicate _predicate = Predicate.DEFAULT;
122     /**
123      * Value can be number, string or nil This can be null.
124      */
125     private Object operand = true;
126 
127     /**
128      * Create sense that will evaluate true based on result of called primitive.
129      * The primitive has no parameters.
130      *
131      * @param senseName name of primitive
132      */
133     public Sense(String senseName) {
134         this(new PrimitiveCall(senseName));
135     }
136 
137     /**
138      * Create sense that will evaluate based on result of called primitive some
139      * parameters
140      *
141      * @param senseCall details of call primitive
142      */
143     public Sense(PrimitiveCall senseCall) {
144         this.senseCall = senseCall;
145         this.compare = false;
146     }
147 
148     /**
149      *
150      * @param senseCall
151      * @param operand operand that will be parsed and changed into proper object
152      * @param predicate
153      */
154     public Sense(PrimitiveCall senseCall, Object operand, Predicate predicate) {
155         assert predicate != null;
156 
157         this.senseCall = senseCall;
158         this.compare = true;
159         this.operand = operand;
160         this._predicate = predicate;
161     }
162     public static final String psSenseName = "paSenseName";
163     public static final String psArgs = "paSenseArgs";
164     public static final String psPredicateIndex = "paPredicate";
165     public static final String psValue = "paValue";
166     public static final String psType = "paType";
167 
168     /**
169      * Get call to the sense primitive.
170      */
171     public PrimitiveCall getCall() {
172         return senseCall;
173     }
174 
175     /**
176      * Set new sense name if new name matches <tt>IDENT_PATTERN</tt>, name will
177      * be trimmed.
178      *
179      * @param newSenseName
180      */
181     public void setSenseName(String newSenseName) throws InvalidNameException {
182         newSenseName = newSenseName.trim();
183 
184         if (!newSenseName.matches(IDENT_PATTERN)) {
185             throw InvalidNameException.create(newSenseName);
186         }
187 
188         String oldSenseName = this.senseCall.getName();
189 
190         this.senseCall = new PrimitiveCall(newSenseName, senseCall.getParameters());
191         this.firePropertyChange(Sense.psSenseName, oldSenseName, newSenseName);
192     }
193 
194     /**
195      * Used in Node.Properties XXX: Do more properly with custom editor
196      *
197      * @return
198      */
199     public String getValueString() {
200         if (operand == null) {
201             return "nil";
202         }
203         if (operand instanceof String) {
204             return '"' + operand.toString() + '"';
205         }
206         return operand.toString();
207     }
208 
209     /**
210      * Get operand that should be used for evaluating this sense. Default value
211      * of operand is true.
212      *
213      * @return Object representing operand, true, false, string, int, double,
214      * even null is valid operand.
215      */
216     public Object getOperand() {
217         return operand;
218     }
219 
220     /**
221      * Set value of argument (as class, e.g. Boolean, String, Integer...).
222      *
223      * @param newValue number, string or nil
224      */
225     public void setOperand(Object newValue) {
226         firePropertyChange(Sense.psValue, operand, operand = newValue);
227     }
228 
229     public Integer getPredicateIndex() {
230         return this._predicate.getId();
231     }
232 
233     public Predicate getPredicate() {
234         return this._predicate;
235     }
236 
237     /**
238      * Set the predicate in the sense. If set to DEFAULT predicate, it won't be
239      * shown.
240      *
241      * @param newPredicate non null
242      */
243     public void setPredicate(Predicate newPredicate) {
244         compare = true;
245         this._predicate = newPredicate;
246         this.firePropertyChange(Sense.psPredicateIndex, null, newPredicate.getId());
247     }
248 
249     /**
250      * Set predicate, use index to array Predicate.values()[index]
251      *
252      * @param newPredicateIndex index to new predicate
253      */
254     public void setPredicateIndex(Integer newPredicateIndex) {
255         if (newPredicateIndex != null) {
256             this._predicate = Predicate.values()[newPredicateIndex];
257             this.firePropertyChange(Sense.psPredicateIndex, null, newPredicateIndex);
258         } else {
259             this._predicate = Predicate.DEFAULT;
260             this.firePropertyChange(Sense.psPredicateIndex, null, Predicate.DEFAULT.getId());
261         }
262     }
263 
264     @Override
265     public Arguments getArguments() {
266         return senseCall.getParameters();
267     }
268 
269     public void setArguments(Arguments newArguments) {
270         String senseName = senseCall.getName();
271         Arguments oldArguments = senseCall.getParameters();
272         this.senseCall = new PrimitiveCall(senseName, newArguments);
273 
274         this.firePropertyChange(Sense.psArgs, oldArguments, newArguments);
275     }
276 
277     /**
278      * Get parameters of the node this sense is part of, basically only C
279      * returns something non-empty. Parameters are specified after "vars"
280      * keyword, e.g in (C vars($a=1, $b=2) (elements ( ((choice (trigger
281      * ((sense($b)))) action($a) )) ))), the $a and $b are parameters that can
282      * be used in senses or actions.
283      *
284      * @return Parameters or null if this sense doesn't have parents.
285      */
286     private FormalParameters getParentParameters() {
287         PoshElement parent = getParent();
288 
289         if (parent == null) {
290             return null;
291         }
292 
293         if (parent instanceof DriveElement) {
294             return new FormalParameters();
295         }
296         if (parent instanceof DriveCollection) {
297             return new FormalParameters();
298         }
299         if (parent instanceof CompetenceElement) {
300             CompetenceElement cel = (CompetenceElement) parent;
301             Competence competence = cel.getParent();
302             return competence.getParameters();
303         }
304         throw new IllegalStateException("Unexpected parent " + parent.getClass().getCanonicalName());
305     }
306 
307     /**
308      * TODO: Correctly document, changed in hurry. This is not correct,. we just
309      * call PoshParser Take the input that represents the sense in one way or
310      * another and set this sense to the values found in the input.
311      *
312      * Possible inputs: senseName (e.g. 'cz.cuni.Health') | senseName value
313      * (e.g. 'cz.cuni.BadlyHurt True') | senseName predicate value (e.g.
314      * 'cz.cuni.Health &lt; 90'). In the second case, the default predicate is
315      * equality. Value can be bool, number or string in double quotes(it will be
316      * properly unescaped).
317      *
318      * @param input
319      * @throws InvalidNameException When name of the sense is not valid.
320      * @throws InvalidFormatException When wrong number of tokens in the input.
321      */
322     public void parseSense(String input) throws ParseException {
323         // XXX: quick and dirty hack for full sense
324         String adjustedInput = '(' + input + ')';
325         PoshParser parser = new PoshParser(new StringReader(adjustedInput));
326         FormalParameters parameters = getParentParameters();
327         Sense parsedSense = parser.fullSense(parameters);
328         changeTo(parsedSense);
329     }
330 
331     /**
332      * Take other sense and change all attributes in the sense to be same as in
333      * other sense.
334      *
335      * @param other
336      */
337     public void changeTo(Sense other) throws InvalidNameException {
338         this.compare = other.compare;
339         this.setSenseName(other.getName());
340         this.setArguments(other.senseCall.getParameters());
341         this.setOperand(other.getOperand());
342         this.setPredicateIndex(other.getPredicateIndex());
343     }
344 
345     /**
346      * Return text representation in posh friendly manner. Examples: (senseName)
347      * (senseName 2 !=) (senseName "Ahoj")
348      *
349      * @return
350      */
351     @Override
352     public String toString() {
353         String res = "(" + senseCall;
354 
355         if (compare) {
356             if (operand instanceof String) {
357                 res += " \"" + operand + '"';
358             } else {
359                 res += " " + operand;
360             }
361             if (_predicate != Predicate.DEFAULT) {
362                 res += " " + _predicate;
363             }
364         }
365 
366         return res + ")";
367     }
368 
369     @Override
370     public String getName() {
371         return senseCall.getName();
372     }
373 
374     /**
375      * XXX: Not implemented, because I don't have a usecase, but interface has
376      * it.
377      *
378      * Finds all senses in the plan with the same name as this sense and changes
379      * their name to newName. } + fire property.
380      *
381      * @param newName What will be new name of all senses with old name.
382      */
383     @Override
384     public void rename(String newName) throws InvalidNameException, CycleException, DuplicateNameException {
385         throw new UnsupportedOperationException("Not yet implemented");
386     }
387 
388     @Override
389     public List<PoshElement> getChildDataNodes() {
390         return Collections.<PoshElement>emptyList();
391     }
392 
393     @Override
394     public boolean moveChild(int newIndex, PoshElement child) {
395         throw new UnsupportedOperationException();
396     }
397     public static final DataFlavor dataFlavor = new DataFlavor(Sense.class, "sense");
398 
399     @Override
400     public DataFlavor getDataFlavor() {
401         return dataFlavor;
402     }
403 
404     @Override
405     public LapType getType() {
406         return LapType.SENSE;
407     }
408 
409     /**
410      * Get human readable representation of this sense.
411      *
412      * Examples: 'working', 'cz.Health &gt; 10', 'cz.Distance("ArmyBot",
413      * $cutoff=12) &lt; 10'
414      *
415      * @return Human readable representation of the sense.
416      */
417     public String getRepresentation() {
418         return getRepresentation(getName());
419     }
420 
421     /**
422      * Get string representation of the sense, i.e. sensecall predicate value,
423      * but instead of sense name, use the passed name.
424      *
425      * Examples: 'working', 'cz.Health &gt; 10', 'cz.Distance("ArmyBot",
426      * $cutoff=12) &lt; 10'
427      *
428      *
429      * @param name Name that is used instead of name in the {@link PrimitiveCall}
430      * @return Human readable representation of the sense.
431      */
432     public String getRepresentation(String name) {
433         StringBuilder representation = new StringBuilder(name);
434         Arguments args = senseCall.getParameters();
435         if (!args.isEmpty()) {
436             representation.append('(').append(args.toString()).append(')');
437         }
438 
439         boolean predicateIsEqual = _predicate == Predicate.EQUAL || _predicate == Predicate.DEFAULT;
440         boolean valueIsTrue = Boolean.TRUE.equals(operand);
441 
442         if (!(predicateIsEqual && valueIsTrue)) {
443             representation.append(_predicate);
444             representation.append(operand);
445         }
446         return representation.toString();
447     }
448 
449     @Override
450     public int compareTo(Sense o) {
451         return this.getRepresentation().compareTo(o.getRepresentation());
452     }
453 
454     /**
455      * Get {@link Trigger} this sense is part of. {@link Trigger} by itself is
456      * not a {@link PoshElement}, but it is part of some other element (e.g. {@link DriveCollection}
457      * or {@link CompetenceElement})
458      *
459      * @return The trigger this sense belongs
460      * @throws IllegalArgumentException If parent is null or I forgot some
461      * possible parent.
462      */
463     public Trigger<?> getTrigger() {
464         PoshElement senseParent = this.getParent();
465         Trigger<?> senseTrigger;
466         if (senseParent instanceof DriveCollection) {
467             DriveCollection dc = (DriveCollection) senseParent;
468             senseTrigger = dc.getGoal();
469         } else if (senseParent instanceof CompetenceElement) {
470             CompetenceElement celParent = (CompetenceElement) senseParent;
471             senseTrigger = celParent.getTrigger();
472         } else if (senseParent instanceof DriveElement) {
473             DriveElement driveParent = (DriveElement) senseParent;
474             senseTrigger = driveParent.getTrigger();
475         } else {
476             throw new IllegalArgumentException("Unexpected parent of sense " + this.getRepresentation() + ": " + senseParent);
477         }
478         return senseTrigger;
479     }
480 
481     /**
482      * This is a common method for removing the sense from its parent. There are
483      * multiple possible parents (DC, CE, DE or no parent) and this method will
484      * handle them all. Once the method is finished, this sense is no longer
485      * child of its original parent and its parent is null.
486      *
487      * When calling this method, parent must not be null.
488      */
489     public void removeFromParent() {
490         assert getParent() != null;
491         Trigger<?> senseTrigger = getTrigger();
492         senseTrigger.remove(this);
493     }
494 }