1 /*********************************************************************
2 Copyright 2012, Ralph Ronnquist.
4 This file is part of GORITE.
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.
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.
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 **********************************************************************/
20 package com.intendico.gorite.addon;
22 import com.intendico.gorite.*;
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.
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.
35 * <p> The following is an illustration of using SubGoal.
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" );
41 * addPlan( new Plan( "use SubGoal for something" ) {
43 * // Propagate intention branch cancellation to sub goal, if any
44 * public void cancelled(Goal.Instance which) {
45 * SubGoal.cancel( which );
48 * // Plan body in Java, with sub goals:
50 * // + Perform goal1 in state 0 with goal1;
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)
56 * // + Perform goal3 in state 12, and swap its PASSED and FAILED
57 * // result to be our result
59 * public States execute(Data data) {
60 * SubGoal sub = SubGoal.get( data );
61 * if ( sub.inProgress( goal1, 0 ) )
63 * while ( sub.progress() < 12 ) {
65 * if ( sub.inProgress( goal2 ) )
67 * } catch (LoopEndException e) {
68 * System.err.println( "END at state " + sub.progress() );
73 * boolean pass = false;
74 * if ( sub.inProgress( goal3, 12 ) ) {
75 * if ( sub.state() != FAILED ) {
81 * return pass? PASSED : FAILED;
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.
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
101 public class SubGoal {
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
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)
121 public Goal.Instance current;
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
130 * @see #inProgress(Goal,int)
131 * @see #inProgress(Goal)
133 public Goal.States result;
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.
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
149 public static SubGoal get(Data data) {
150 SubGoal sub = tryGet( data );
151 return ( sub == null )? new SubGoal( data ) : sub;
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:
162 * SubGoal sub = SubGoal.tryGet( data );
163 * if ( sub == null ) {
164 * sub = new MySubGoal( data );
168 public static SubGoal tryGet(Data data) {
169 return (SubGoal) data.getValue( data.thread_name );
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.
181 public SubGoal(Data d) {
183 data.setValue( data.thread_name, this );
187 * Clean up by forgetting the data element named by the current
188 * {@link Data#thread_name}.
191 data.forget( data.thread_name );
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
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( "\"" );
209 i = head.lastIndexOf( "\"", i - 1 );
212 head = head.substring( 0, i );
213 current = goal.instantiate( head + ":" + count, data );
215 result = current.perform( data );
216 if ( result == Goal.States.PASSED || result == Goal.States.FAILED ) {
220 return result != Goal.States.PASSED;
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;
228 public boolean inProgress(Goal goal,int at)
229 throws LoopEndException, ParallelEndException {
230 return count == at? inProgress( goal ) : false;
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.
240 public boolean cancel(int next) {
242 if ( current == null )
246 result = Goal.States.CANCEL;
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}.
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 );
260 sub.cancel( sub.count + 1 );
263 data.setThreadName( old );
267 * Utility method that returns the current progress count.
269 public int progress() {
274 * Utility method that returns the cached sub intention state.
276 public Goal.States state() {
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.
285 public boolean isActive() {
286 return current != null;