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.LinkedList;
9   import java.util.List;
10  
11  /**
12   * Sense is basically a function in Python or Java. It is called and it returns some result.
13   * This class is representation of sense, but it can also take an argument and compare it with predicate.
14   *
15   * During execution POSH engine calls the sense and compares return value
16   * with argument by predicate. If no argument was passed, use true, if no predicate
17   * was passed, use ==.
18   *
19   * @author Honza
20   */
21  public class Sense extends NamedLapElement implements Comparable<Sense> {
22  
23      /**
24       * Various predicates used in <tt>Sense</tt>
25       * @author Honza
26       */
27      public enum Predicate {
28  
29          EQUAL(new String[]{"==", "="}),
30          NOT_EQUAL("!="),
31          LOWER("<"),
32          GREATER(">"),
33          LOWER_OR_EQUAL("<="),
34          GREATER_OR_EQUAL(">="),
35          DEFAULT("==");				// default is "=="
36          private String[] stringForm = null;
37  
38          private Predicate(String form) {
39              stringForm = new String[]{form};
40          }
41  
42          private Predicate(String[] form) {
43              stringForm = form;
44          }
45  
46          /**
47           * Get Predicate enum from passed string
48           * @param str passed predicate in string form
49           * @return Predicate
50           */
51          public static Predicate getPredicate(String str) {
52              if (str == null)
53                  return Predicate.DEFAULT;
54  
55              str = str.trim();
56  
57              for (Predicate p : Predicate.values()) {
58                  for (String form : p.stringForm) {
59                      if (form.equals(str)) {
60                          return p;
61                      }
62                  }
63              }
64              throw new IllegalArgumentException("String \"" + str + "\" is not a predicate.");
65          }
66  
67          /**
68           * Get integer id of predicate. Used because of stupid combo box property pattern in NB.
69           * @return
70           */
71          public int getId() {
72              for (int i = 0; i < Predicate.values().length; i++) {
73                  if (values()[i] == this) {
74                      return i;
75                  }
76              }
77              throw new RuntimeException("Predicate \"" + this.toString() + "\" wasn't found in list of predicates.");
78          }
79  
80          @Override
81          public String toString() {
82              return stringForm[0];
83          }
84      }
85  
86      
87  
88      /**
89       * Name of primitive that will be called along with list of parameters
90       */
91      private PrimitiveCall senseCall;
92      /**
93       * When evaluating result of sense, should I compare value returned by the primitive
94       * or should I just evaluate primtive and return?
95       */
96      private boolean compare = true;
97      /**
98       * Comparator used for comparison of value returned by primitive and operand
99       */
100     private Predicate _predicate = Predicate.DEFAULT;
101     /**
102      * Value can be number, string or nil
103      * This can be null.
104      */
105     private Object operand = true;
106 
107     /**
108      * Create sense that will evaluate true based on result of called primitive.
109      * The primitive has no parameters.
110      * @param senseName name of primitive
111      */
112     public Sense(String senseName) {
113         this(new PrimitiveCall(senseName));
114     }
115 
116     /**
117      * Create sense that will evaluate based on result of called primitive
118      * some parameters
119      * @param senseCall details of call primitive
120      */
121     public Sense(PrimitiveCall senseCall) {
122         this.senseCall = senseCall;
123         this.compare = false;
124     }
125 
126     /**
127      *
128      * @param senseCall
129      * @param operand operand that will be parsed and changed into proper object
130      * @param predicate
131      */
132 //    public Sense(PrimitiveCall senseCall, String operand, Predicate predicate) {
133   //      this(senseCall, Result.parseValue(operand), predicate);
134     //}
135 
136     public Sense(PrimitiveCall senseCall, Object operand, Predicate predicate) {
137         assert predicate!= null;
138 
139         this.senseCall = senseCall;
140         this.compare = true;
141         this.operand = operand;
142         this._predicate = predicate;
143     }
144 
145     public static final String psSenseName = "paSenseName";
146     public static final String psArgs = "paSenseArgs";
147     public static final String psPredicateIndex = "paPredicate";
148     public static final String psValue = "paValue";
149     public static final String psType = "paType";
150     /*
151      */
152 
153     public String getSenseName() {
154         return senseCall.getName();
155     }
156 
157     /**
158      * Get param
159      */
160     public PrimitiveCall getSenseCall() {
161         return senseCall;
162     }
163     
164     /**
165      * Set new sense name if new name matches <tt>IDENT_PATTERN</tt>, name will be trimmed.
166      * @param newSenseName
167      */
168     public void setSenseName(String newSenseName) throws InvalidNameException {
169         newSenseName = newSenseName.trim();
170 
171         if (!newSenseName.matches(IDENT_PATTERN)) {
172             throw InvalidNameException.create(newSenseName);
173         }
174 
175         String oldSenseName = this.senseCall.getName();
176 
177         this.senseCall = new PrimitiveCall(newSenseName, senseCall.getParameters());
178         this.firePropertyChange(Sense.psSenseName, oldSenseName, newSenseName);
179     }
180 
181     /**
182      * Used in Node.Properties
183      * XXX: Do more properly with custom editor
184      * @return
185      */
186     public String getValueString() {
187         if (operand == null) {
188             return "nil";
189         }
190         if (operand instanceof String) {
191             return '"' + operand.toString() + '"';
192         }
193         return operand.toString();
194     }
195 
196     /**
197      * Get operand that should be used for evaluating this sense. Default value 
198      * of operand is true.
199      * 
200      * @return Object representing operand, true, false, string, int, double, 
201      * even null is valid operand.
202      */
203     public Object getOperand() {
204         return operand;
205     }
206 
207     /**
208      * Set value of argument (as class, e.g. Boolean, String, Integer...).
209      *
210      * @param newValue number, string or nil
211      */
212     public void setOperand(Object newValue) {
213         firePropertyChange(Sense.psValue, operand, operand = newValue);
214     }
215 
216     public Integer getPredicateIndex() {
217         return this._predicate.getId();
218     }
219 
220     public Predicate getPredicate() {
221         return this._predicate;
222     }
223 
224     /**
225      * Set the predicate in the sense. If set to DEFAULT predicate, it won't be shown.
226      * @param newPredicate non null
227      */
228     public void setPredicate(Predicate newPredicate) {
229         compare = true;
230         this._predicate = newPredicate;
231         this.firePropertyChange(Sense.psPredicateIndex, null, newPredicate.getId());
232     }
233 
234     /**
235      * Set predicate, use index to array Predicate.values()[index]
236      * @param newPredicateIndex index to new predicate
237      */
238     public void setPredicateIndex(Integer newPredicateIndex) {
239         if (newPredicateIndex != null) {
240             this._predicate = Predicate.values()[newPredicateIndex];
241             this.firePropertyChange(Sense.psPredicateIndex, null, newPredicateIndex);
242         } else {
243             this._predicate = Predicate.DEFAULT;
244             this.firePropertyChange(Sense.psPredicateIndex, null, Predicate.DEFAULT.getId());
245         }
246     }
247 
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 }