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;
22 import java.util.Vector;
23 import java.util.Hashtable;
24 import java.util.Iterator;
25 import java.util.Observable;
26 import java.util.Observer;
27 import com.intendico.data.Ref;
28 import com.intendico.data.RuleSet;
29 import com.intendico.data.Rule;
30 import com.intendico.data.Query;
31 import com.intendico.gorite.Goal.Instance;
32 import com.intendico.gorite.addon.TodoGroupSkipBlocked;
33 import com.intendico.gorite.addon.TodoGroupRoundRobin;
34 import com.intendico.gorite.addon.TodoGroupParallel;
37 * The Performer class is base class for {@link Team} member
38 * implementations. Conceptually a performer is considered to be the
39 * agent for {@link Goal} executions, and it contains the belief
40 * structures, if any, that agent reasoning may refer to.
42 * <p> A Performer is a {@link Capability} in that it has {@link Goal}
43 * hierarchies, and inner capabilities that define its reasoning
46 * <p> The goals of performer may be grouped to belong to named "to-do
47 * groups", which are represented by the inner class {@link
48 * Performer.TodoGroup}. The goals of a TodoGroup are managed such
49 * that only one at a time is performed, and the group is associated
50 * with meta-level reasoning for deciding which goal to perform next.
52 public class Performer extends Capability {
55 * The {@link Data.Element} name of the Todogroup object concerned
56 * when performing TodoGroups meta-level reasoning.
58 public static final String TODOGROUP = "todogroup";
61 * Holds the Performer's name.
66 * Default constructor. When this is used, the {@link #name} field
67 * should be assigned before the performer is made to fill a role.
74 * Constructor with {@link #name} given.
76 public Performer(String n) {
82 * Returns a text representation of this performer.
84 public String toString() {
85 return getClass().getName() + ": " + name;
89 * Overrides {@link Capability#addCapability} so as to link up
90 * added capabilities with this performer.
92 public void addCapability(Capability c) {
93 c.setPerformer( this );
94 super.addCapability( c );
98 // The performer's rule set, if any.
102 * The Performer's rule set.
104 public RuleSet rule_set = new RuleSet() {{
105 addObserver( new Observer() {
106 public void update(Observable x,Object y) {
113 * Utility method to apply all rules exhaustively.
115 public void propagateBeliefs() {
120 // TodoGroup based execution support
124 * A constant goal name for a generic TodoGroup meta goal.
125 * Presently "todogroup meta goal".
127 public final static String TODOGROUP_META_GOAL =
128 TODOGROUP + " meta goal";
131 * This performer's {link TodoGroup} objects.
133 public Hashtable/*<String,TodoGroup>*/ todogroups =
134 new Hashtable/*<String,TodoGroup>*/();
137 * Utility method to find a named {@link TodoGroup}. This method
138 * will create a new {@link TodoGroup} rather than return
139 * <tt>null</tt>, and the new {@link TodoGroup} will have its
140 * {@link TodoGroup#meta_goal} string set to <tt>null</tt> by
141 * default (meaning no meta goal), unless the system property
142 * <tt>gorite.todo.classic</tt> is set, in which case {@link
143 * #TODOGROUP_META_GOAL} is used as meta goal name.
144 * @param rn the {@link TodoGroup} name
145 * @return the {@link TodoGroup} object of that name among this
146 * performer's {@link #todogroups}.
148 public TodoGroup getTodoGroup(String rn) {
151 TodoGroup r = (TodoGroup) todogroups.get( rn );
153 if ( Goal.isTracing() ) {
154 System.err.println( "-- " + name + " adds TodoGroup " + rn );
157 if ( System.getProperty( "gorite.todo.classic" ) != null )
158 mg = TODOGROUP_META_GOAL;
159 r = new TodoGroup( rn, mg );
166 * Utility method to add a TodoGroup with given name and meta goal.
168 public void addTodoGroup(String n,String mg) {
169 addTodoGroup( new TodoGroup( n, mg ) );
173 * Utility method to add a given TodoGroup.
175 public void addTodoGroup(TodoGroup r) {
176 if ( todogroups == null )
177 todogroups = new Hashtable/*<String,TodoGroup>*/();
178 todogroups.put( r.name, r );
182 * Control flag for TodoGroup execution.
184 private long todo_added = 0;
187 * Utility method to add an instance to its todogroup.
189 public Goal.States execute(
190 boolean reentry,Goal.Instance instance,String group)
191 throws LoopEndException, ParallelEndException
193 if ( Goal.isTracing() )
194 System.err.println( "-- execute: " + group + " " + reentry );
196 return getTodoGroup( group ).execute( reentry, instance );
200 * Index of TodoGroup to execute next.
202 private int todo_index = 0;
205 * Control method to run all todogroups until stopped or blocked.
207 synchronized public Goal.States runPerformer() {
208 if ( Goal.isTracing() )
210 "-- " + name + " running " +
211 ( todogroups == null? 0 : todogroups.size() ) );
213 if ( todogroups == null || todogroups.size() == 0 )
214 return Goal.States.BLOCKED;
215 boolean running = true;
216 Goal.States s = Goal.States.BLOCKED;
218 long todo_added_cache = todo_added;
219 TodoGroup [] groups = (TodoGroup []) todogroups.values().toArray(
220 new TodoGroup [ todogroups.size() ] );
223 for ( int i = 0; i < groups.length; i++ ) {
224 if ( todo_index >= groups.length )
226 s = groups[ todo_index++ ].runGroup();
227 if ( Goal.isTracing() )
228 System.err.println( "-- runGroup = " + s );
230 running = todo_added != todo_added_cache;
231 if ( s == Goal.States.BLOCKED ) {
234 if ( s == Goal.States.STOPPED )
237 s = Goal.States.BLOCKED;
240 if ( Goal.isTracing() )
241 System.err.println( "-- " + name + " (" + s + ")" );
246 * The TodoGroup class provides a meta-level goal execution
247 * facility, allowing multiple parallel executions be
250 * <p> A Goal that has its {@link Goal#group} attribute set will
251 * be executed by adding its execution instance to the nominated
252 * TodoGroup. The instance execution will be progressed as a
253 * TodoGroup execution task, when the execution thread gets around
256 * <p> The TodoGroup is associated with a meta goal {@link
257 * #meta_goal}, which is performed by the execution machinery when
258 * the TodoGroup state changes: when a goal instance is added,
259 * when its execution gets blocked, and when it completes. The
260 * meta goal execution may inspect the TodoGroup and promote any
261 * of the pending goal instance executions to be the one to
264 public class TodoGroup implements Observer {
267 * A name attribute for the group. There is no particular
268 * significance in this name except that it uniquely
269 * identifies a TodoGroup object for a {@link Performer}.
271 public String name = null;
274 * This names the {@link BDIGoal} to be invoked when {@link
275 * Goal.Instance} objects are added to, or removed from the
276 * TodoGroup, as well as when the intention execution returns
277 * blocked. The meta goal is invoked for the purpose of
278 * deciding which intention to pursue next.
280 * <p> The default name is {@link
281 * Performer#TODOGROUP_META_GOAL}.
283 * <p> When the meta goal is invoked, it is provided with the
284 * following data elements:
286 * <dt>named by {@link Goal#PERFORMER}</dt><dd>this {@link
287 * Performer} object</dd>
288 * <dt>named by {@link #TODOGROUP}</dt><dd>this TodoGroup
290 * <dt>"added"</dt><dd>{@link Vector} of the {@link
291 * Goal.Instance} objects just added to the group, if any.</dd>
292 * <dt>"removed"</dt><dd>{@link Vector} of the {@link
293 * Goal.Instance} objects just removed from the group, if any.</dd>
294 * <dt>"top"</dt><dd>the current top-of-stack {@link
295 * Goal.Instance} object. When an instance terminates, it is
296 * added to the "removed" set, then removed from the stack
297 * before the meta goal gets invoked.</dd>
300 * <p> Note that the "added" and "removed" collections have
301 * been processed, and their contents have been added and
302 * removed respectively before the meta goal is performed.
304 * <p> A meta goal plan typically uses {@link #promote} to
305 * change the {@link #stack}. Still, no other code is supposed
306 * to operate on the stack concurrently, so the meta goal plan
307 * may change the stack in more complex ways.
309 * @see TodoGroupSkipBlocked
310 * @see TodoGroupRoundRobin
311 * @see TodoGroupParallel
313 public String meta_goal = null;
316 * Constructor given name and meta goal name. If the meta goal
317 * name is null, then the default meta goal name ({@link
318 * Performer#TODOGROUP_META_GOAL}) is used.
320 public TodoGroup(String n,String mg) {
326 * This is the stack of executions in this TodoGroup.
328 public Vector/*<Goal.Instance>*/ stack =
329 new Vector/*<Goal.Instance>*/();
331 public Vector/*<Goal.Instance>*/ added =
332 new Vector/*<Goal.Instance>*/();
334 public Vector/*<Goal.Instance>*/ removed =
335 new Vector/*<Goal.Instance>*/();
338 * Keeping track of an ongoing meta goal execution. This is in
339 * particular used if a meta goal execution is blocked or
340 * stopped, in which case the pursuit of TodoGroup tasks is
341 * stopped. The ongoing meta goal execution must terminate
342 * (passed, failed or cancel) before the next TodoGroup task
345 * <p> Note that new TodoGroup tasks can be added during an
346 * ongoing meta goal execution, and that this will not trigger
347 * any new meta goal execution. However, it will cause {@link
348 * #added_while_ongoing} to be set, which in turn results in
349 * an additional meta goal execution to follow the ongoing
352 public Goal.Instance ongoing_meta_goal = null;
355 * A flag that indicates that a TodoGroup task was added
356 * during an ongoing meta goal execution. This will cause a
357 * subsequent meta goal execution before any TodoGroup task is
360 public boolean added_while_ongoing = false;
363 * Invoked prior to pursuing the top element of the
364 * stack. This method adds the added instances onto the stack,
365 * cleares out the removed instances. It creates a meta goal
366 * instance if either forced is true, or the some instance was
369 * <p> Note that this method will change the {@link #stack} by
370 * removing completed tasks, and actually adding tasks pending
371 * to be added. This happens before the invocation of the meta
374 private Goal.Instance enterGroup(boolean forced) {
375 Vector/*<Goal.Instance>*/ coming = null;
376 Vector/*<Goal.Instance>*/ gone = null;
378 // pick up the added and removed instances
379 // the added vector changes by parallel threads
380 synchronized ( added ) {
382 added = new Vector/*<Goal.Instance>*/();
384 removed = new Vector/*<Goal.Instance>*/();
387 // handle removed instances
388 for ( Iterator/*<Goal.Instance>*/ i = gone.iterator();
390 Goal.Instance instance = (Goal.Instance) i.next();
391 instance.data.deleteObserver( instance.thread_name, this );
394 // handle added instances
395 for ( Iterator/*<Goal.Instance>*/ i = coming.iterator();
397 Goal.Instance instance = (Goal.Instance) i.next();
398 stack.add( instance );
399 instance.data.addObserver( instance.thread_name, this );
402 if ( meta_goal == null )
405 if ( coming.size() + gone.size() == 0 ) {
408 if ( System.getProperty( "gorite.todo.classic" ) != null )
412 if ( Goal.isTracing() ) {
414 "-- " + Performer.this.name + "/" + name +
415 " meta goal " + added + " \"" + meta_goal + "\"" );
418 Goal.Instance mg = new BDIGoal( meta_goal )
419 .instantiate( name, null );
420 mg.performer = Performer.this;
422 .setValue( Goal.PERFORMER, Performer.this )
423 .setValue( TODOGROUP, this )
424 .setValue( "added", coming )
425 .setValue( "removed", gone )
426 .setValue( "top", stack.size() == 0? null : stack.get( 0 ) );
427 mg.data.link( mg.thread_name );
433 * A transient handle to the most recent blocking intention on
436 public Goal.Instance blocking = null;
439 * Run the group. This means to run its top-of-stack goal
440 * instance. If that passes or fails, it is removed from the
441 * group, and the meta goal is called upon to possibly
442 * re-order remaining goal instances. If it returns STOPPED,
443 * the group execution returns STOPPED. If it returns BLOCKED,
444 * the meta goal is invoked in order to possibly re-arrange
445 * the group, and if so, the group execution continues with
446 * the new top goal instance. Otherwise the group execution
449 public Goal.States runGroup() {
450 if ( Goal.isTracing() ) {
452 "-- TodoGroup " + Performer.this.name + "/" + name +
453 " " + stack.size() );
456 if ( ongoing_meta_goal != null ) {
457 if ( Goal.isTracing() ) {
459 "-- TodoGroup " + Performer.this.name +
460 "/" + name + " ongoing meta goal " +
463 Goal.States s = ongoing_meta_goal.action();
464 if ( Goal.isTracing() ) {
466 "-- " + Performer.this.name + "/" + name +
467 " meta goal " + " \"" + meta_goal + "\" " +
470 if ( s == Goal.States.STOPPED ) {
471 continue; // Don't interrupt meta goal
473 if ( s == Goal.States.BLOCKED ) {
477 ongoing_meta_goal = enterGroup( ongoing_meta_goal == null );
478 if ( ongoing_meta_goal != null )
480 if ( stack.size() == 0 )
481 return Goal.States.BLOCKED;
483 Goal.Instance x = (Goal.Instance) stack.get( 0 );
485 if ( x == blocking ) {
486 if ( ! x.data.isRunning( x.thread_name ) ) {
487 return Goal.States.BLOCKED;
489 if ( Goal.isTracing() ) {
490 System.err.println( "-- BLOCKING " + x.head +
491 " without trigger" );
494 //return Goal.States.BLOCKED;
497 Goal.States s = x.action();
498 if ( Goal.isTracing() ) {
499 System.err.println( "-- noting " + x.head + " " + s );
501 if ( s == Goal.States.STOPPED ) {
504 if ( s == Goal.States.BLOCKED ) {
508 // s == Goal.States.PASSED
509 // s == Goal.States.FAILED
510 // s == Goal.States.CANCEL
511 x.setMonitoring( s );
512 if ( stack.remove( x ) )
518 * Adds a goal for execution as part of this todo
519 * group. Returns the instance monitoring state.
521 public Goal.States execute(
522 boolean reentry,Goal.Instance instance)
523 throws LoopEndException, ParallelEndException
528 synchronized ( added ) {
529 instance.setMonitoring( Goal.States.STOPPED );
530 added.add( instance );
534 instance.throwSavedException();
536 return instance.state;
540 * Utility method to obtain stack size.
547 * Utility method to access the i:th stack element.
548 * @throws ArrayIndexOutOfBoundsException if the stack is
549 * empty, or i is negative.
551 public Instance get(int i) {
552 return (Instance) stack.get( i );
556 * Utility method to remove the i:th stack element.
557 * @throws ArrayIndexOutOfBoundsException if appropriate
559 public Instance remove(int i) {
560 return (Instance) stack.remove( i );
564 * Utility method to insert an element to be the i:th stack
566 * @throws ArrayIndexOutOfBoundsException if appropriate
568 public void add(int i,Instance x) {
573 * Utility method to move a selected {@link Goal.Instance} to
574 * be the one to pursue next.
576 public void promote(int index) {
578 stack.insertElementAt( stack.remove( index ), 0 );
582 * Utility method to move the top {@link Goal.Instance} to
585 public void rotate() {
586 if ( stack.size() > 1 )
587 stack.add( stack.remove( 0 ) );
591 /// Observer interface implementation
595 * Implements {@link Observer#update} by pretending that a
596 * todo group is added, as way of signalling that the
597 * performer should process its todogroups again
599 public void update(Observable x,Object y) {
603 } // End class TodoGroup
606 * The {@link Executor} for this performer.
608 public Executor executor = null;
611 * Utility method to signal the {@link #executor} that this
612 * performer wants some attention. This will use the current
613 * executor, and attach to the default executor if none is
616 public void signalExecutor() {
617 if ( executor == null ) {
618 executor = Executor.getDefaultExecutor();
619 shareInquirables( null );
620 executor.addPerformer( this );
626 * Utility method to instantiate the goal for given the head, and
627 * then keep invoking the perform method with the given data
628 * context, until it passes or fails.
630 synchronized public boolean performGoal(Goal goal,String head,Data d) {
633 Goal.Instance instance = goal.instantiate( head, d );
634 Goal.States s = Goal.States.PASSED;
636 if ( Goal.isTracing() )
637 System.err.println( "** perform " + instance.head );
638 d.replaceValue( Goal.PERFORMER, this );
639 s = instance.perform( d );
640 if ( s == Goal.States.PASSED || s == Goal.States.FAILED )
642 boolean idle = s == Goal.States.BLOCKED;
643 if ( executor != null ) {
644 if ( executor.runPerformersBlocked() )
648 instance.waitDone(); // ignore result
650 if ( executor != null ) {
651 while ( executor.runPerformersBlocked() )
654 return s == Goal.States.PASSED;
655 } catch (LoopEndException e) {
658 } catch (ParallelEndException e) {
665 * Alternative performGoal method, with input and output held as
666 * Vector<Ref> collections.
668 public boolean performGoal(
669 Goal goal,String head, Vector/*<Ref>*/ ins,Vector/*<Ref>*/ outs) {
672 boolean b = performGoal( goal, head, d );
678 * Alternative performGoal method, with goal given as a {@link
679 * String}, implying a BDI goal, and input and output held as
680 * Vector<Ref> collections.
682 public boolean performGoal(
683 String goal,String head, Vector/*<Ref>*/ ins,Vector/*<Ref>*/ outs) {
684 return performGoal( new BDIGoal( goal ), head, ins, outs );
688 * Returns true if the performer execution should change focus.
690 public boolean changeFocus() {
691 return executor != null && executor.changeFocus();
695 * Returns a {@link RoleFilling} object for filling a given {@link
696 * Team.Role}. The object represents that this performer fills the
697 * given {@link Team.Role}. This method may be overridden in a
698 * specific performer, either as way of providing an extended
699 * {@link RoleFilling} object, or to possibly refuse the {@link
700 * Team.Role} filling, by returning <em>null</em>.
702 public RoleFilling fillRole(Team.Role r) {
703 return new RoleFilling( r );
707 * This class is used for representing this performer when acting
708 * in a given {@link Team.Role}. It in particular provides a
709 * {@link #lookup(String)} method that provides a combined plan
710 * lookup in both the filled {@link Team.Role} (qua {@link
711 * Capability}) and the enclosing {@link Performer} (qua {@link
712 * Capability}), and thereby extending the performer's capability
713 * with the role plans.
715 public class RoleFilling extends Capability {
718 * The {@link Team.Role} being filled.
720 public Team.Role role;
723 * Constructor. The enclosing performer is set to fill the
726 public RoleFilling(Team.Role r) {
731 * Extends the base class method {@link Capability#lookup} by
732 * wrapping the result goals into {@link TransferGoal} goals.
734 public Vector/*<Goal>*/ lookup(String name) {
735 Vector/*<Goal>*/ v = Performer.this.lookup( name );
736 Vector/*<Goal>*/ vx = role.lookup( name );
737 vx.addAll( super.lookup( name ) );
738 for ( Iterator/*<Goal>*/ gi = v.iterator(); gi.hasNext(); ) {
739 vx.add( goalForPerformer( (Goal) gi.next() ) );
745 * Extends the base class method {@link Capability#hasGoals} by
746 * formarding to the performer
748 public boolean hasGoal(String name) {
749 return role.hasGoal( name ) ||
750 super.hasGoal( name ) ||
751 Performer.this.hasGoal( name );
755 * Returns the {@link Performer} of this RoleFilling.
757 public Performer getPerformer() {
758 return Performer.this;
762 * Returns a text representation of this performer object.
764 public String toString()
766 return "role filler " + Performer.this;
771 * Utility method to obtain the {@link Team} that has established
772 * a {@link Team.Role} filler under a given name in the {@link
775 static public Team team(String rolename,Data d) {
776 RoleFilling r = (RoleFilling) d.getValue( rolename );
777 return r.role.team();
781 * Utility method to create a {@link TransferGoal} object aimed
784 public Goal goalForPerformer(Goal g) {
785 return new TransferGoal( this, g );
789 * A method that returns {@link #name}.
791 public String getName() {
796 /// Plan choice support
800 * The association between (BDI) goals and their plan choice
801 * modifiers. These take effect in the BDI plan choice processing,
802 * to provide the decision for choosing a next applicable plan
803 * instance. The modifier should be either a {@link
804 * java.util.Random Random} object
805 * (for making a random draw among highest precedence plan
806 * options0, or a {@link String} (for making the choice by meta
807 * level plan processing).
811 public Hashtable/*<String,Object>*/ plan_choices = null;
814 * Utility method to declare a plan choice modifier for a
815 * goal. The modifier should be either a {@link java.util.Random
816 * Random} object, or a {@link String}.
820 public void setPlanChoice(String goal,Object choice_method) {
821 if ( plan_choices == null ) {
822 plan_choices = new Hashtable/*<String,String>*/();
824 plan_choices.put( goal, choice_method );
828 * Utility method to obtain the plan choice modifier, if any,
829 * associated with the given goal.
831 public Object getPlanChoice(String goal) {
832 return plan_choices == null? null : plan_choices.get( goal );