View Javadoc

1   package cz.cuni.amis.pogamut.sposh.ut2004;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.FileInputStream;
6   import java.io.IOException;
7   import java.io.InputStream;
8   import java.io.InputStreamReader;
9   import java.io.PrintWriter;
10  import java.io.StringReader;
11  import java.io.StringWriter;
12  import java.util.ArrayList;
13  import java.util.Collections;
14  import java.util.Comparator;
15  import java.util.LinkedList;
16  import java.util.List;
17  import java.util.logging.Level;
18  
19  import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
20  import cz.cuni.amis.pogamut.sposh.elements.ParseException;
21  import cz.cuni.amis.pogamut.sposh.elements.PoshParser;
22  import cz.cuni.amis.pogamut.sposh.elements.PoshPlan;
23  import cz.cuni.amis.pogamut.sposh.engine.FireResult;
24  import cz.cuni.amis.pogamut.sposh.engine.PoshEngine;
25  import cz.cuni.amis.pogamut.sposh.engine.PoshEngine.EvaluationResultInfo;
26  import cz.cuni.amis.pogamut.sposh.engine.timer.ITimer;
27  import cz.cuni.amis.pogamut.sposh.engine.timer.SystemClockTimer;
28  import cz.cuni.amis.pogamut.sposh.executor.ILogicWorkExecutor;
29  import cz.cuni.amis.pogamut.sposh.executor.IWorkExecutor;
30  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
31  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004BotLogicController;
32  
33  /**
34   * Logic controller that utilizes sposh engine for decision making of bot in UT2004
35   * environment.
36   * <p/>
37   * Sposh requires two things: plan and primitives. The plan is provided by 
38   * {@link SposhLogicController#getPlan() } method and are supplied by {@link IWorkExecutor}.
39   * {@link IWorkExecutor} is instantiated during first call of logic, so if the varioud
40   * modules are already initialized.
41   * <p/>
42   * If needed, override {@link SposhLogicController#createTimer() }, but it
43   * shouldn't be needed.
44   * @author Honza H.
45   */
46  @AgentScoped
47  public abstract class SposhLogicController<BOT extends UT2004Bot, WORK_EXECUTOR extends IWorkExecutor> extends UT2004BotLogicController<BOT> {
48  
49      public static final String SPOSH_LOG_CATEGORY = "SPOSH";
50      /**
51       * Posh engine that is evaluating the plan.
52       */
53      private List<PoshEngine> engines = new LinkedList<PoshEngine>();
54      /**
55       * Primitive executor that is executing the primitves when engine requests it,
56       * passes the variables and returns the value of executed primitive.
57       */
58      private WORK_EXECUTOR workExecutor;
59      /**
60       * Timer for posh engine, for things like timeouts and so on.
61       */
62      private ITimer timer;
63  
64      /**
65       * {@inheritDoc}.
66       *
67       * Yaposh egnines that will be used by this bot are created here (so {@link #getEngines()
68       * } doesn't return empty list after this).
69       *
70       * @param bot
71       */
72      @Override
73      public void initializeController(BOT bot) {
74          super.initializeController(bot);
75          createEngines();
76      }
77  
78      private void createEngines() {
79          try {
80              int planId = 0;
81              for (String planSrc : getPlans()) {
82                  engines.add(createEngine(planId++, planSrc));
83              }
84          } catch (IOException ex) {
85              bot.getLogger().getCategory(SPOSH_LOG_CATEGORY).log(Level.SEVERE, "IOException {0} - Stacktrace:\n{1}", new Object[]{ex.getMessage(), getStackTrace(ex)});
86              throw new IllegalStateException(ex);
87          }
88      }
89  
90      private String getStackTrace(Exception ex) {
91          StringWriter sw = new StringWriter();
92          ex.printStackTrace(new PrintWriter(sw));
93          return sw.toString();
94      }
95      
96      /**
97       * Create {@link IWorkExecutor} that will execute primitives contained in the plan.
98       * This method will be called only once.
99       * @return executor to execute primitives.
100      */
101     protected abstract WORK_EXECUTOR createWorkExecutor();
102 
103     /**
104      * Get work executor. If work executor is not yet created, create one using
105      * {@link SposhLogicController#createWorkExecutor() }.
106      * @return work executor for this bot controller.
107      */
108     protected final WORK_EXECUTOR getWorkExecutor() {
109         if (workExecutor == null) {
110             workExecutor = createWorkExecutor();
111         }
112         return workExecutor;
113     }
114 
115     /**
116      * Logic method evaluates all Yaposh plans in same order as they were
117      * specified. 
118      */
119     @Override
120     public final void logic() {
121         logicBeforePlan();
122         for (int engineId = 0; engineId < engines.size(); engineId++) {
123             iterateEngine(engines.get(engineId), engineId);
124         }
125         logicAfterPlan();
126     }
127 
128     private void iterateEngine(PoshEngine engine, int engineId) {
129         engine.getLog().log(Level.INFO, "Invoking Yaposh engine " + engineId + " for plan: " + engine.getName());
130         while (true) {
131             EvaluationResultInfo result = engine.evaluatePlan(getWorkExecutor());
132             engine.getLog().fine("Element result: " + result.result + " / " + result.type);
133             if (result.result == PoshEngine.EvaluationResult.ELEMENT_FIRED) {
134                 if (result.type != null && (result.type == FireResult.Type.CONTINUE || result.type == FireResult.Type.FOLLOW || result.type == FireResult.Type.FULFILLED
135                 		                    || result.type == FireResult.Type.SURFACE_CONTINUE || result.type == FireResult.Type.FAILED)) {
136                     engine.getLog().info("Plan evaluation continues...");
137                     continue;
138                 }
139             }
140             break;
141         }
142         engine.getLog().info("Plan evaluation end.");
143     }
144     
145     /**
146      * Method that is triggered every time the plan for executor is evaluated.
147      * It is triggered right before the plan evaluation.
148      * Currently, it checks if {@link SposhLogicController#workExecutor} is a
149      * {@link ILogicWorkExecutor} and if it is, it executes {@link ILogicWorkExecutor#logicBeforePlan() }.
150      */
151     protected void logicBeforePlan() {
152         if (workExecutor instanceof ILogicWorkExecutor) {
153             ILogicWorkExecutor logicExecutor = (ILogicWorkExecutor) workExecutor;
154             logicExecutor.logicBeforePlan();
155         }
156     }
157 
158     /**
159      * Method that is triggered every time the plan for executor is evaluated.
160      * It is triggered right after the plan evaluation.
161      * Currently, it checks if {@link SposhLogicController#workExecutor} is a
162      * {@link ILogicWorkExecutor} and if it is, it executes {@link ILogicWorkExecutor#logicBeforePlan() }.
163      */
164     protected void logicAfterPlan() {
165         if (workExecutor instanceof ILogicWorkExecutor) {
166             ILogicWorkExecutor logicExecutor = (ILogicWorkExecutor) workExecutor;
167             logicExecutor.logicAfterPlan();
168         }
169     }
170 
171     /**
172      * Create timer for posh engine. By default, use {@link SystemClockTimer}.
173      * @see SposhLogicController#getTimer() 
174      * @return
175      */
176     protected ITimer createTimer() {
177         return new SystemClockTimer();
178     }
179 
180     /**
181      * Get timer that is used by posh engine to make sure timeouts and other stuff
182      * that requires time are working properly.
183      * @return
184      */
185     protected final ITimer getTimer() {
186         if (timer == null) {
187             timer = createTimer();
188         }
189         return timer;
190     }
191 
192     /**
193      * Parse the supplied plan.
194      * @param planSource plan source to be parsed
195      * @return parsed plan
196      * @throws ParseException if there is an syntax error in the plan.
197      */
198     private PoshPlan parsePlan(String planSource) throws ParseException {
199         StringReader planReader = new StringReader(planSource);
200         PoshParser parser = new PoshParser(planReader);
201         return parser.parsePlan();
202     }
203 
204     /**
205      * Create one engine for passed @planSrc.
206      *
207      * @param engineId Id of created engine
208      * @param planSrc Source of the plan for the engine.
209      * @throws IllegalArgumentException If @planSrc can't be parsed.
210      */
211     private PoshEngine createEngine(int engineId, String planSrc) {
212         try {
213             PoshPlan plan = parsePlan(planSrc);
214             return new PoshEngine(engineId, plan, getTimer(), bot.getLogger().getCategory(SPOSH_LOG_CATEGORY));
215         } catch (ParseException ex) {
216             bot.getLogger().getCategory(SPOSH_LOG_CATEGORY).log(Level.SEVERE, "Parse exceptions during parsing plan:\n{0}\nStacktrace:\n{1}", new Object[]{planSrc, getStackTrace(ex)});
217             throw new IllegalArgumentException(ex);
218         }
219     }
220 
221     /**
222      * Get engines used by this bot.
223      *
224      * @return Empty list if engine wasn't yet created(they are created in {@link SposhLogicController#initializeController(UT2004Bot)
225      * } ) or the engines.
226      */
227     protected final List<PoshEngine> getEngines() {
228         return engines;
229     }
230 
231     /**
232      * Get all Yaposh plans this bot is supposed to execute. The plans will be
233      * executed in same order as in the returned list. Easiest way is to use
234      * {@link #getPlanFromResource(java.lang.String) }, {@link #getPlanFromFile(java.lang.String)},
235      * {@link #getPlanFromStream(java.io.InputStream) or {@link #getPlansFromDirectory(String)}.
236      * }.
237      *
238      * @return List of sources of the plans
239      */
240     protected abstract List<String> getPlans() throws IOException;
241 
242     /**
243      * Read POSH plan from the stream and return it. Close the stream.
244      * @param in Input stream from which the plan is going to be read
245      * @return Text of the plan, basically content of the stream
246      * @throws IOException If there is some error while reading the stream
247      */
248     protected final String getPlanFromStream(InputStream in) throws IOException {
249         BufferedReader br = new BufferedReader(new InputStreamReader(in));
250 
251         StringBuilder plan = new StringBuilder();
252         String line;
253         try {
254             while ((line = br.readLine()) != null) {
255                 plan.append(line);
256             }
257         } finally {
258             br.close();
259         }
260 
261         return plan.toString();
262     }
263     
264     /**
265      * Reads all '.lap' file from specified directory. They are alphabetically sorted (ascending order).
266      * @param directoryPath
267      * @return
268      * @throws IOException
269      */
270     protected final List<String> getPlansFromDirectory(String directoryPath) throws IOException {
271     	List<File> files = new ArrayList<File>();
272     	for (File file : new File(directoryPath).listFiles()) {
273     		if (".lap".equals(file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(".")))) {
274     			files.add(file);
275     		}
276     	}
277     	Collections.sort(files, new Comparator<File>() {
278 			@Override
279 			public int compare(File o1, File o2) {
280 				return o1.getAbsolutePath().compareTo(o2.getAbsolutePath());
281 			}
282     		
283     	});
284     	List<String> result = new ArrayList<String>();
285     	for (File file : files) {
286     		result.add(getPlanFromFile(file.getAbsolutePath()));
287     	}
288     	return result;
289     }
290 
291     /**
292      * Read POSH plan from the file and return it.
293      * @param filename Path to the file that contains the POSH plan.
294      * @return Text of the plan, basically content of the file
295      * @throws IOException If there is some error while reading the stream
296      */
297     protected final String getPlanFromFile(String filename) throws IOException {
298         FileInputStream f = new FileInputStream(filename);
299         return getPlanFromStream(f);
300     }
301 
302     /**
303      * Get POSh plan from resource int the same jar as the class.
304      * <p>
305      * <pre>
306      *  // Plan is stored in package cz.cuni.amis.pogamut.testbot under name poshPlan.lap
307      *  // This can get the file from .jar or package structure
308      *  getPlanFromResource("cz/cuni/amis/pogamut/testbot/poshPlan.lap");
309      * </pre>
310      * @param resourcePath Path to the plan in some package
311      * @return Content of the plan.
312      * @throws IOException if something goes wrong, like file is missing, hardisk has blown up ect.
313      */
314     protected final String getPlanFromResource(String resourcePath) throws IOException {
315         ClassLoader cl = this.getClass().getClassLoader();
316         return getPlanFromStream(cl.getResourceAsStream(resourcePath));
317     }
318 }