capture
[rrq/gorite.git] / com / intendico / gorite / BDIGoal.java
1 /*********************************************************************
2 Copyright 2012, Ralph Ronnquist.
3
4 This file is part of GORITE.
5
6 GORITE is free software: you can redistribute it and/or modify it
7 under the terms of the Lesser GNU General Public License as published
8 by the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 GORITE is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14 License for more details.
15
16 You should have received a copy of the Lesser GNU General Public
17 License along with GORITE.  If not, see <http://www.gnu.org/licenses/>.
18 **********************************************************************/
19
20 package com.intendico.gorite;
21
22 import com.intendico.data.Query;
23 import com.intendico.data.Ref;
24
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.Vector;
28 import java.util.Random;
29
30 /**
31  * A BDIGoal is achieved by means of finding and performing a goal
32  * hierarchy as named by the BDI goal in the {@link Capability} that
33  * is the value of the {@link Data.Element} named by the BDIGoal's
34  * {@link #control} attribute. By default, the {@link #PERFORMER} data
35  * name is used, which is maintained by the execution machinery to
36  * resolve to the current performer. The BDIGoals created by the
37  * {@link TeamGoal} execution uses the role name as data name, which
38  * then is set up to be the role filling.
39  *
40  * <p> A BDIGoal is executed in a sequence of steps in an attempt to
41  * find a goal hierarchy for the named goal that acheives it, or more
42  * precisely, that executes without returning FAILED. The goal
43  * hierarchy alternatives are found via the {@link
44  * Capability#lookup(String)} method of the executing {@link
45  * Performer}. Any such alternative that implements the {@link
46  * Context} interface is considered a plan whose {@link
47  * Context#context(Data)} method defines its applicable variants.
48  *
49  * <p> The BDIGoal collates all applicable plan options, then selects
50  * one of them as an attempt to achieve the goal. If that fails, then
51  * the plan option is remembered, and the BDIGoal again collates all
52  * applicable, non-failed plan options, picks one and tries that. This
53  * repeats until either one plan execution succeeds (returns PASSED),
54  * or the plan options are exhausted, in which case the BDIGoal fails.
55  * 
56  * <p> The choice of which among the applicable plan options to use
57  * can be modified via the plan choice settings. By default, the plan
58  * options are queried for their {@link Precedence#precedence(Data)}
59  * values, and the first of highest precedence option is
60  * choosen. However, if the goal is associated with a {@link Random}
61  * object, then the option is choosen by a random draw among the
62  * highest precedence options. Further, if the goal is associated with
63  * a plan choice goal name, then that plan choice goal is performed
64  * for effectuating the plan choice (instead of using precedence
65  * values).
66  *
67  * <p> A BDIGoal that has a {@link #specific} object set, makes that
68  * object available for instance execution as a data element named by
69  * the BDIInstance head name, which for invoked sub goals is
70  * everything up to the last '*' of their heads.
71  *
72  * @see Plan
73  * @see PlanChoiceGoal
74  * @see ContextualGoal
75  * @see Performer#getPlanChoice(String)
76  * @see Performer#setPlanChoice(String,Object)
77  * @see TeamGoal
78  */
79 public class BDIGoal extends Goal {
80
81     /**
82      * The name of the data element whose value is the name of the
83      * current role for the executing performer.
84      */
85     public static final String ROLE = "current role";
86
87     /**
88      * Constructor. 
89      */
90     public BDIGoal(String n) {
91         super( n );
92         setGoalControl( PERFORMER );
93     }
94
95     /**
96      * Cache of construction object (when not a String).
97      */
98     public Object specific;
99
100     /**
101      * Constructor using an object other than String. Then the class
102      * name of the object is used as goal name, and the object is held
103      * as {@link #specific}. However, if the given object is a {@link
104      * String}, then its value (rather than its type) is used as goal
105      * name anyhow.
106      */
107     public BDIGoal(Object x) {
108         this( ( x instanceof String )? (String) x : x.getClass().getName() );
109         specific = x instanceof String? null : x;
110     }
111
112     /**
113      * Creates and returns an instance object for achieving
114      * a BDIGoal.
115      */
116     public Instance instantiate(String head,Data d) {
117         return new BDIInstance( head );
118     }
119
120     /**
121      * Utility method to add a Goal unless it's contained among
122      * failed.
123      */
124     static public void maybeAdd(
125         Goal g,Vector/*<Goal>*/ v,Vector/*<Goal>*/ failed) {
126         if ( failed == null || ! failed.contains( g ) ) {
127             v.add( g );
128         }
129     }
130
131     /**
132      * Expand context sensitive alternative plans. This method gets
133      * invoked with the current selection of plans matching to the
134      * BDIGoal to achieve, the current collection of tried but failed
135      * plan variants, and the current {@link Data}. It processes all
136      * plan contexts so as to produce the currently possible
137      * alternative contextual plan invocations, by determining
138      * validating bindings for the plan's context queries.
139      *
140      * <p> The class {@link ContextualGoal} is used to represent a
141      * plan variant, which consists of the plan together with the
142      * query {@link Ref} object binding. This takes care of
143      * presenting the binding in the {@link Data} when the plan is
144      * invoked, to unset this binding from the {@link Data} if the
145      * plan execution fails, and to extract new bindings from the
146      * {@link Data} when the plan succeeds.
147      *
148      * <p> This method recognises the {@link Context#EMPTY} query
149      * as marker that a plan does not have any applicable variant,
150      * and it also catches the {@link Context.None} exception for
151      * the same purpose.
152      *
153      * <p> Plan variants are filtered against the failed set, to
154      * avoid the same plan variant be attempted more than once.
155      *
156      * @see Context
157      * @see ContextualGoal
158      * @see BDIInstance#action
159      */
160     static public Vector/*<Goal>*/ applicable(
161         Vector/*<Goal>*/ plans, Vector/*<Goal>*/ failed, Data data) {
162         Vector/*<Goal>*/ v = new Vector/*<Goal>*/();
163
164         for ( Iterator/*<Goal>*/ i = plans.iterator(); i.hasNext(); ) {
165             Goal goal = (Goal) i.next();
166             if ( ! ( goal instanceof Context ) ) {
167                 maybeAdd(
168                     new ContextualGoal( null, null, goal, data ),
169                     v, failed );
170                 continue;
171             }
172             try {
173                 Query query = ((Context) goal).context( data );
174                 if ( query == null ) {
175                     maybeAdd(
176                         new ContextualGoal( null, null, goal, data ),
177                         v, failed );
178                     continue;    
179                 }
180                 if ( query != Context.EMPTY ) {
181                     Vector/*<Ref>*/ vars =
182                         query.getRefs( new Vector/*<Ref>*/() );
183                     Vector/*<Ref>*/ orig = Ref.copy( vars );
184                     query.reset();
185                     while ( query.next() ) {
186                         maybeAdd(
187                             new ContextualGoal( orig, vars, goal, data ),
188                             v, failed );
189                     }
190                 }
191             } catch (Context.None e) {
192                 // No applicable context
193             } catch (Throwable e) {
194                 e.printStackTrace();
195                 System.err.println( "Ignored plan " + goal );
196             }
197         }
198         return v;
199     }
200
201     /**
202      * Return the plan choice for this goal, relative a given root
203      * capability executing the goal. This treats 
204      */
205     public Object getPlanChoice(Capability root) {
206         return root.getPerformer().getPlanChoice( getGoalName() );
207     }
208
209     /**
210      * Implements a BDI method choice.
211      */
212     public class BDIInstance extends Instance {
213
214         /**
215          * The goal alternatives. (Called "relevant set" in BDI
216          * terminology)
217          */
218         public Vector/*<Goal>*/ relevant;
219
220         /**
221          * The capability hierarchy offering goal methods.
222          */
223         public Capability root;
224
225         /**
226          * The tried and failed goals.
227          */
228         public Vector/*<Goal>*/ failed = new Vector/*<Goal>*/();
229
230         /**
231          * The count of attempts.
232          */
233         public int count = 0;
234
235         /**
236          * The current goal.
237          */
238         Goal goal;
239
240         /**
241          * The current goal instance.
242          */
243         Instance instance = null;
244
245         /**
246          * Determine the context sensitive alternatives by invoking
247          * {@link #applicable}
248          *
249          * <p> At end, this method invokes {@link #precedenceOrder} on
250          * the collection of applicable plan variants, which sorts the
251          * plan variants by descending precedence.
252          * @see Context
253          * @see ContextualGoal
254          * @see #precedenceOrder
255          * @see BDIInstance#action
256          */
257         public Goal contextual(
258             Vector/*<Goal>*/ set,Vector/*<Goal>*/ failed,Data data) {
259             Vector/*<Goal>*/ v = applicable( set, failed, data );
260
261             // If there is a plan choice goal for this goal, then let
262             // it make the choice.
263             if ( plan_choice instanceof String ) {
264                 return new PlanChoiceGoal(
265                     root.getPerformer(), (String) plan_choice, v, failed );
266             }
267             return precedenceOrder( v, data );
268         }
269
270         /**
271          * Apply precedence ordering destructively to the given goal
272          * set.
273          *
274          * @see Precedence
275          */
276         public Goal precedenceOrder(Vector/*<Goal>*/ set,Data data) {
277             int level = 0;
278             Vector/*<Goal>*/ best = new Vector();
279             for ( Iterator/*<Goal>*/ i = set.iterator(); i.hasNext(); ) {
280                 Goal g = (Goal) i.next();
281                 int p = g instanceof Precedence? 
282                     ((Precedence) g).precedence( data ) :
283                     Plan.DEFAULT_PRECEDENCE ;
284                 if ( best.size() == 0 ) {
285                     level = p;
286                     best.add( g );
287                 } else if ( p > level ) {
288                     best.clear();
289                     level = p;
290                     best.add( g );
291                 } else if ( p == level ) {
292                     best.add( g );
293                 }
294             }
295             if ( best.size() == 0 )
296                 return null;
297             if ( best.size() == 1 || ! ( plan_choice instanceof Random ) )
298                 return (Goal) best.get( 0 );
299             return (Goal) best.get(
300                 ((Random) plan_choice).nextInt( best.size() ) );
301         }
302
303         /**
304          * Holds the plan choice goal, or null.
305          */
306         public Object plan_choice;
307
308         /**
309          * Constructor.
310          */
311         public BDIInstance(String h) {
312             super( h );
313         }
314
315         /**
316          * Cancel the execution.
317          */
318         public void cancel() {
319             if ( instance != null )
320                 instance.cancel();
321         }
322
323         /**
324          * Instantiates and performs sub goals in sequence, until the
325          * first one that does not fail. If a sub goal fails, then
326          * that is caught, and the next sub goal in sequence is
327          * instantiated and performed.
328          */
329         public States action(String head,Data data)
330             throws LoopEndException, ParallelEndException {
331             if ( root == null ) {
332                 root = (Capability) data.getValue( getGoalControl() );
333                 if ( root == null ) {
334                     System.err.println(
335                         "** Missing capability '" + getGoalControl() +
336                         "' for " + BDIGoal.this.toString( head ) );
337                     return States.FAILED;
338                 }
339                 plan_choice = getPlanChoice( root );
340             }
341             if ( goal == null ) {
342                 if ( isTracing() ) {
343                     System.err.println(
344                         "** Lookup \"" + getGoalName() + "\"" );
345                 }
346                 data.setArgs( head, specific );
347                 goal = contextual(
348                     root.lookup( getGoalName() ), failed, data );
349                 if ( goal == null && isTracing() ) {
350                     System.err.println(
351                         "** Goal \"" + getGoalName() + "\" unknown " );
352                 }
353             }
354
355             while ( goal != null ) {
356                 if ( instance == null ) {
357                     instance = goal.instantiate( head + "*" + count, data );
358                     count += 1;
359                 }
360                 if ( isTracing() ) {
361                     System.err.println(
362                         "** " + nameString( getGoalName() ) +
363                         " attempt " + count );
364                 }
365                 String was_role = (String) data.getValue( ROLE );
366                 States b;
367                 data.setValue( ROLE, getGoalControl() );
368                 try {
369                     b = instance.perform( data );
370                 } finally {
371                     data.restoreValue( ROLE, was_role );
372                 }
373                 //
374                 // Note, the following doesn't happen if instance.perform()
375                 // above throws an exception.
376                 //
377                 if ( b != States.FAILED )
378                     return b;
379                 instance = null;
380                 if ( goal instanceof PlanChoiceGoal ) {
381                     PlanChoiceGoal pg = (PlanChoiceGoal) goal;
382                     if ( pg.choice == null )
383                         break;
384                     if ( pg.done == States.PASSED )
385                         failed.add( pg.choice );
386                 } else {
387                     failed.add( goal );
388                 }
389                 goal = contextual(
390                     root.lookup( getGoalName() ), failed, data );
391             }
392             return States.FAILED;
393         }
394     }
395 }