View Javadoc

1   package cz.cuni.amis.pogamut.sposh.ut2004;
2   
3   import java.lang.reflect.Constructor;
4   import java.lang.reflect.InvocationTargetException;
5   import java.util.Collections;
6   import java.util.Set;
7   
8   import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
9   import cz.cuni.amis.pogamut.sposh.context.Context;
10  import cz.cuni.amis.pogamut.sposh.context.IUT2004Context;
11  import cz.cuni.amis.pogamut.sposh.engine.PoshEngine;
12  import cz.cuni.amis.pogamut.sposh.exceptions.StateInstantiationException;
13  import cz.cuni.amis.pogamut.sposh.executor.IAction;
14  import cz.cuni.amis.pogamut.sposh.executor.ISense;
15  import cz.cuni.amis.pogamut.sposh.executor.IWorkExecutor;
16  import cz.cuni.amis.pogamut.sposh.executor.StateAction;
17  import cz.cuni.amis.pogamut.sposh.executor.StateSense;
18  import cz.cuni.amis.pogamut.sposh.executor.StateWorkExecutor;
19  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
20  import java.io.IOException;
21  import java.util.*;
22  
23  /**
24   * This class should be used as base for bot that utilizes sposh and state primitives.
25   * It is failry simple, it creates {@link PoshEngine}, {@link IWorkExecutor} and {@link Context} during {@link StateSposhLogicController#initializeController(cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot) }.
26   * The {@link IWorkExecutor} takes all primitives from the plan and automatically
27   * tries to instantiate them.
28   *
29   * The automatic instantiation expects that names of the primitives are fully
30   * qualified names of classes (e.g. cz.cuni.pogamut.senses.Fail) that implement 
31   * {@link ISense} for senses and {@link IAction} for actions. The classes are 
32   * expected to have public constructor with one parameter that is of type CONTEXT
33   * (or its parent). It may be convinient to use classes {@link StateAction} and 
34   * {@link StateSense} as basic classes for your own custom primitives.
35   *
36   * Custom instantiation can be utilized in method {@link StateSposhLogicController#customPrimitiveInstantiation(cz.cuni.amis.pogamut.sposh.executor.StateWorkExecutor, java.util.Set, java.util.Set)  },
37   * simply insert your own primitives into the {@link StateWorkExecutor}. When automatic
38   * instantiation detects, that primitive with some name (probaly name that is not
39   * FQN of class) is already defined in the executor, the name is skipped (IOW: if
40   * you add sense <b>hurt</b> in the custom instantion, it won't cause error later one,
41   * although it is not defined by any class).
42   * 
43   * @author Honza
44   */
45  @AgentScoped
46  public abstract class StateSposhLogicController<BOT extends UT2004Bot, CONTEXT extends IUT2004Context> extends SposhLogicController<BOT, StateWorkExecutor> {
47  
48      /** Context for states. */
49      protected CONTEXT context;
50  
51      /**
52       * Initialize logic controller=call super initialization and create context
53       * and other stuff that is needed to have.
54       * @param bot
55       */
56      @Override
57      public void initializeController(BOT bot) {
58          super.initializeController(bot);
59          context = createContext();
60          // This will make sure work executor is instantiated
61          getWorkExecutor();
62      }
63  
64  
65      @Override
66      public void finishControllerInitialization() {
67      	super.finishControllerInitialization();
68          context.finishInitialization();
69      }
70  
71      /**
72       * Get context.
73       * @return get context, possibly create one
74       */
75      public final CONTEXT getContext() {
76          return context;
77      }
78  
79      /**
80       * To be overriden in children, this method enables user to instantiate
81       * primitives in any way it desires. If you keep it empty, logic will try
82       * to instantiate primitivies by their names and annotations.
83       * @param executor executor that will be used for primitives (empty, no primitive should be defined yet)
84       * @param actions set of actions used in the plan (unmodifiable)
85       * @param senses set of senses used in the plan (unmodifiable)
86       */
87      protected void customPrimitiveInstantiation(StateWorkExecutor executor, Set<String> actions, Set<String> senses) {
88          // Nothing to do here, override in children.
89      }
90  
91      /**
92       * Find class that is a state primitivie corresponding to passed action name.
93       * @param actionName name of action, should be FQN.
94       * @return class of the action
95       */
96      private Class getActionClass(String actionName) {
97          try {
98              return Class.forName(actionName);
99          } catch (ClassNotFoundException ex) {
100             throw new StateInstantiationException("Unable to find state class for action \"" + actionName + "\"", ex);
101         }
102     }
103 
104     /**
105      * Find class that is a state primitivie corresponding to passed sense name.
106      * @param senseName name of action, should be FQN.
107      * @return class of the sense
108      */
109     private Class getSenseClass(String senseName) {
110         try {
111             return Class.forName(senseName);
112         } catch (ClassNotFoundException ex) {
113             throw new StateInstantiationException("Unable to find state class for sense \"" + senseName + "\"", ex);
114         }
115     }
116 
117     /**
118      * Instantiate primitive of passed class. If there will be an error, write it into log and
119      * throw runtime exception.
120      * @param <T> Returning class of the primitive
121      * @param cls class type of primitive
122      * @return created instance.
123      */
124     private <T> T instantiatePrimitive(Class<T> cls) {
125         String name = cls.getName();
126         try {
127             Constructor primitiveConstructor = null;
128             Constructor<?>[] constructors = cls.getConstructors();
129             for (Constructor<?> constructor : constructors) {
130                 Class[] constructorParameters = constructor.getParameterTypes();
131                 if (constructorParameters.length != 1) {
132                     continue;
133                 }
134                 if (constructorParameters[0].isAssignableFrom(this.getContext().getClass())) {
135                     primitiveConstructor = constructor;
136                 }
137             }
138             if (primitiveConstructor == null) {
139                 throw new StateInstantiationException("Primitive \"" + name + "\" doesn't have a constructor that has exactly one parameter of class " + this.getContext().getClass().getName());
140             }
141             return (T) primitiveConstructor.newInstance(this.getContext());
142         } catch (InstantiationException ex) {
143             throw new StateInstantiationException("Unable to instantiate primitive \"" + name + "\" (" + ex.getMessage() + ")", ex);
144         } catch (IllegalAccessException ex) {
145             throw new StateInstantiationException("Illegal access protection for primitive \"" + name + "\"", ex);
146         } catch (IllegalArgumentException ex) {
147             throw new StateInstantiationException("Primitive \"" + name + "\" doesn't accept bot class (" + this.bot.getClass().getName() + ") in the constructor.", ex);
148         } catch (InvocationTargetException ex) {
149             throw new StateInstantiationException("Constructor of primitive \"" + name + "\" has thrown an exception (" + ex.getMessage() + ")", ex);
150         }
151     }
152 
153     private Set<String> getActionNames() {
154         Set<String> actionNames = new HashSet<String>();
155         for (PoshEngine engine : getEngines()) {
156             Set<String> engineActions = engine.getPlan().getActionsNames();
157             actionNames.addAll(engineActions);
158         }
159         return actionNames;
160     }
161 
162     private Set<String> getSensesNames() {
163         Set<String> senseNames = new HashSet<String>();
164         for (PoshEngine engine : getEngines()) {
165             Set<String> engineSenses = engine.getPlan().getSensesNames();
166             senseNames.addAll(engineSenses);
167         }
168         return senseNames;
169     }
170     
171     @Override
172     protected StateWorkExecutor createWorkExecutor() {
173         StateWorkExecutor executor = new StateWorkExecutor(bot.getLogger().getCategory(SPOSH_LOG_CATEGORY));
174 
175         // Get names of all primitives;
176         Set<String> actions = getActionNames();
177         Set<String> senses = getSensesNames();
178 
179         // Check that there isn't a sense with same name as action
180         for (String senseName : senses) {
181             if (actions.contains(senseName))
182                 throw new StateInstantiationException("List of senses and " + senseName);
183         }
184 
185 
186         // Use custom initialization
187         customPrimitiveInstantiation(executor, Collections.unmodifiableSet(actions), Collections.unmodifiableSet(senses));
188 
189         // Get classses associated with the names
190         for (String name : actions) {
191             if (executor.isNameUsed(name)) {
192                 throw new StateInstantiationException("Action instantiation: Primitive with name \"" + name + "\" is already in has already used in the executor.");
193             }
194             Class<IAction> cls = getActionClass(name);
195             IAction action = instantiatePrimitive(cls);
196             executor.addAction(name, action);
197         }
198         for (String name : senses) {
199             if (executor.isNameUsed(name)) {
200                 throw new StateInstantiationException("Sense instantiation: Primitive with name \"" + name + "\" is already in has already used in the executor.");
201             }
202             Class<ISense> cls = getSenseClass(name);
203             ISense sense = instantiatePrimitive(cls);
204             executor.addSense(name, sense);
205         }
206 
207         return executor;
208     }
209 
210     /**
211      * Take the plan from {@link #getPlan() } and return it as the single plan
212      * of the bot.
213      *
214      * @see SposhLogicController#getPlans()
215      * @return One plan from from {@link #getPlan() }.
216      */
217     @Override
218     protected List<String> getPlans() throws IOException {
219         return Arrays.asList(getPlan());
220     }
221     
222     /**
223      * Get Yaposh plan this bot is supposed to execute. Easiest way is to use
224      * {@link #getPlanFromResource(java.lang.String) }, {@link #getPlanFromFile(java.lang.String)
225      * } or {@link #getPlanFromStream(java.io.InputStream)
226      * }.
227      *
228      * @return Sources of the plan
229      */
230     protected abstract String getPlan() throws IOException;
231     
232     /**
233      * Create context for this logic controller.
234      * @return new logic controller.
235      */
236     protected abstract CONTEXT createContext();
237 }