capture
[rrq/gorite.git] / com / intendico / gorite / addon / SubGoal.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.addon;
21
22 import com.intendico.gorite.*;
23
24 /**
25  * This is a utility class for managing the dynamic state of a sub
26  * goal execution within a (task) Goal's execute method. The execute
27  * method that needs to perform sub goals (typically BDIGoal goals)
28  * will need to obtain its SubGoal object on entry, and then dispatch
29  * to the execution point appropriate for the SubGoal progress count.
30  *
31  * <p> Note that when performing sub goals, it is also appropriate to
32  * re-implement the {@link Goal#cancelled} method for propagating
33  * intention branch cancellation.
34  *
35  * <p> The following is an illustration of using SubGoal.
36  * <pre>
37  * static Goal goal1 = new BDIGoal( "the first sub goal" );
38  * static Goal goal2 = new BDIGoal( "the second sub goal" );
39  * static Goal goal3 = new BDIGoal( "the third sub goal" );
40  *
41  * addPlan( new Plan( "use SubGoal for something" ) {
42  *
43  *     // Propagate intention branch cancellation to sub goal, if any
44  *     public void cancelled(Goal.Instance which) {
45  *         SubGoal.cancel( which );
46  *     }
47  *
48  *     // Plan body in Java, with sub goals:
49  *     //
50  *     // + Perform goal1 in state 0 with goal1;
51  *     //
52  *     // + Perform goal2 repeatedly through states 1-11, but break
53  *     //   the loop and warp to state 12 on a LoopEndException;
54  *     //   (with a trace output to stderr)
55  *     //
56  *     // + Perform goal3 in state 12, and swap its PASSED and FAILED
57  *     //   result to be our result
58  *
59  *     public States execute(Data data) {
60  *         SubGoal sub = SubGoal.get( data );
61  *         if ( sub.inProgress( goal1, 0 ) )
62  *             return sub.state();
63  *         while ( sub.progress() < 12 ) {
64  *             try {
65  *                 if ( sub.inProgress( goal2 ) )
66  *                     return sub.state();
67  *             } catch (LoopEndException e) {
68  *                 System.err.println( "END at state " + sub.progress() );
69  *                 sub.count = 12;
70  *                 break;
71  *             }
72  *         }
73  *         boolean pass = false;
74  *         if ( sub.inProgress( goal3, 12 ) ) {
75  *             if ( sub.state() != FAILED ) {
76  *                 return sub.state();
77  *             }
78  *             pass = true;
79  *         }
80  *         sub.done(); 
81  *         return pass? PASSED : FAILED;
82  *     }
83  * } );
84  * </pre>
85  *
86  * <p> Note that an intention branch may only have one active SubGoal
87  * object at a time, and you have to go out of your way in coding to
88  * juggle multiple concurrently active SubGoal objects. If you want
89  * parallel execution, then you should execute an approprate
90  * ParallelGoal rather than trying to implement your own parallel goal
91  * execution management.
92  *
93  * <p> The sub goal execution may throw an exception, where it in
94  * particular may be a {@link LoopEndException} or a {@link
95  * ParallelEndException}. In most cases, the execute method would
96  * ignore this, and thus allow them to be propagated up through the
97  * invocation. In the example above, the goal2 invocation, being a
98  * loop, catch {@link LoopEndException}, and let that break the loop
99  * sucessfully.
100  */
101 public class SubGoal {
102
103     /**
104      * This is a convenience field that an execution method may use
105      * for marking its progress through its sub goal invocations. Its
106      * value is automatically appended to the trace head for the next
107      * sub goal, and it is incremented when a sub goal execution
108      * terminates.
109      * @see #progress()
110      */
111     public int count;
112
113     /**
114      * This is an internal cache of the {@link
115      * com.intendico.gorite.Goal.Instance Goal.Instance} in progress,
116      * if any. It is publically accessible, but the execution method
117      * should refrain from changing it.
118      * @see #inProgress(Goal,int)
119      * @see #inProgress(Goal)
120      */
121     public Goal.Instance current;
122
123     /**
124      * This is an internal cache of the most recent return value of
125      * the most recent sub goal invocation.  It is publically
126      * accessible, but the execution method should refrain from
127      * changing it. When the sub goal invocation is incomplete
128      * (i.e. either BLOCKED or STOPPED), the execution method should
129      * return this value.
130      * @see #inProgress(Goal,int)
131      * @see #inProgress(Goal)
132      */
133     public Goal.States result;
134
135     /**
136      * This is a handle to the Data object for the executing
137      * intention.  It is publically accessible, but the execution
138      * method should refrain from changing it.
139      */
140     public Data data;
141
142     /**
143      * Utility method that looks up a current SubGoal object in the
144      * given Data, or creates a new one associated with the current
145      * Data. To do so, it uses the Data element named by {@link
146      * Data#thread_name}, which is unique within the intention for the
147      * intention branch.
148      */
149     public static SubGoal get(Data data) {
150         SubGoal sub = tryGet( data );
151         return ( sub == null )? new SubGoal( data ) : sub;
152     }
153
154     /**
155      * Utility method that looks up a current SubGoal object in the
156      * given Data and returns it, or null, if none exists. You'd use
157      * this in combination with a SubGoal extension class, e.g. for
158      * carrying your own progress details. Thus, instead of
159      * using {@link #get} to find the sub goal manager, you would have
160      * a code snippet like the following:
161      * <pre>
162      * SubGoal sub = SubGoal.tryGet( data );
163      * if ( sub == null ) {
164      *     sub = new MySubGoal( data );
165      * }
166      * </pre>
167      */
168     public static SubGoal tryGet(Data data) {
169         return (SubGoal) data.getValue( data.thread_name );
170     }
171
172     /**
173      * Constructor. Normally SubGoal objects are only created
174      * indirectly, either via the {@link #get} method, or via
175      * <tt>super</tt> calls in extension classes. It always end up in
176      * this constructor, which associates the newly created object
177      * with the given {@link Data}, and puts itself as the Data
178      * element named by the current {@link Data#thread_name}, which is
179      * unique for the intention branch.
180      */
181     public SubGoal(Data d) {
182         data = d;
183         data.setValue( data.thread_name, this );
184     }
185
186     /**
187      * Clean up by forgetting the data element named by the current
188      * {@link Data#thread_name}.
189      */
190     public void done() {
191         data.forget( data.thread_name );
192     }
193
194     /**
195      * This method is used by the execution method to trigger and
196      * pursue a sub goal execution. The method returns true if the
197      * sub goal invocation returns without passing (i.e., returns
198      * STOPPED, BLOCKED or FAILED), and it returns false when the sub
199      * goal invocation returns PASSED. Also, when the sub goal returns
200      * PASSED or FAILED, the count gets incremented before this method
201      * returns.
202      */
203     public boolean inProgress(Goal goal)
204         throws LoopEndException, ParallelEndException {
205         if ( current == null ) {
206             String head = data.thread_name;
207             int i = head.lastIndexOf( "\"" );
208             if ( i > 0 )
209                 i = head.lastIndexOf( "\"", i - 1 );
210             i -= 1;
211             if ( i > 0 )
212                 head = head.substring( 0, i );
213             current = goal.instantiate( head + ":" + count, data );
214         }
215         result = current.perform( data );
216         if ( result == Goal.States.PASSED || result == Goal.States.FAILED ) {
217             current = null;
218             count += 1;
219         }
220         return result != Goal.States.PASSED;
221     }
222
223     /**
224      * This method verifies that the progress count is as given,
225      * before pursuing sub goal execution as per {@link
226      * #inProgress(Goal)}. Otherwise the method returns false;
227      */
228     public boolean inProgress(Goal goal,int at)
229         throws LoopEndException, ParallelEndException {
230         return count == at? inProgress( goal ) : false;
231     }
232
233     /**
234      * Utility method to cancel any ongoing sub goal execution, and
235      * force the count to a given state. If there is no sub goal in
236      * progress, the method returns false. Otherwise, that sub goal
237      * execution is cancelled and forgotten, {@link #result} is set to
238      * CANCEL, and true is returned.
239      */
240     public boolean cancel(int next) {
241         count = next;
242         if ( current == null )
243             return false;
244         current.cancel();
245         current = null;
246         result = Goal.States.CANCEL;
247         return true;
248     }
249
250     /**
251      * This method cancels and forgets any sub goal execution in
252      * progress for the given {@link
253      * com.intendico.gorite.Goal.Instance Goal.Instance}.
254      */
255     public static void cancel(Goal.Instance which) {
256         Data data = which.data;
257         String old = data.setThreadName( which.thread_name );
258         SubGoal sub = (SubGoal) data.getValue( which.thread_name );
259         if ( sub != null ) {
260             sub.cancel( sub.count + 1 );
261             sub.done();
262         }
263         data.setThreadName( old );
264     }
265
266     /**
267      * Utility method that returns the current progress count.
268      */
269     public int progress() {
270         return count;
271     }
272
273     /**
274      * Utility method that returns the cached sub intention state.
275      */
276     public Goal.States state() {
277         return result;
278     }
279
280     /**
281      * Utility method that tells whether the current state has been
282      * activated or not; i.e., whether the sub intention for the
283      * current state has been created or not.
284      */
285     public boolean isActive() {
286         return current != null;
287     }
288
289 }