View Javadoc

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