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             if (result.result == PoshEngine.EvaluationResult.ELEMENT_FIRED) {
133                 if (result.type != null && (result.type == FireResult.Type.CONTINUE || result.type == FireResult.Type.FOLLOW || result.type == FireResult.Type.FULFILLED
134                 		                    || result.type == FireResult.Type.SURFACE_CONTINUE || result.type == FireResult.Type.FAILED)) {
135                     engine.getLog().info("Plan evaluation continues...");
136                     continue;
137                 }
138             }
139             break;
140         }
141         engine.getLog().info("Plan evaluation end.");
142     }
143     
144     /**
145      * Method that is triggered every time the plan for executor is evaluated.
146      * It is triggered right before the plan evaluation.
147      * Currently, it checks if {@link SposhLogicController#workExecutor} is a
148      * {@link ILogicWorkExecutor} and if it is, it executes {@link ILogicWorkExecutor#logicBeforePlan() }.
149      */
150     protected void logicBeforePlan() {
151         if (workExecutor instanceof ILogicWorkExecutor) {
152             ILogicWorkExecutor logicExecutor = (ILogicWorkExecutor) workExecutor;
153             logicExecutor.logicBeforePlan();
154         }
155     }
156 
157     /**
158      * Method that is triggered every time the plan for executor is evaluated.
159      * It is triggered right after the plan evaluation.
160      * Currently, it checks if {@link SposhLogicController#workExecutor} is a
161      * {@link ILogicWorkExecutor} and if it is, it executes {@link ILogicWorkExecutor#logicBeforePlan() }.
162      */
163     protected void logicAfterPlan() {
164         if (workExecutor instanceof ILogicWorkExecutor) {
165             ILogicWorkExecutor logicExecutor = (ILogicWorkExecutor) workExecutor;
166             logicExecutor.logicAfterPlan();
167         }
168     }
169 
170     /**
171      * Create timer for posh engine. By default, use {@link SystemClockTimer}.
172      * @see SposhLogicController#getTimer() 
173      * @return
174      */
175     protected ITimer createTimer() {
176         return new SystemClockTimer();
177     }
178 
179     /**
180      * Get timer that is used by posh engine to make sure timeouts and other stuff
181      * that requires time are working properly.
182      * @return
183      */
184     protected final ITimer getTimer() {
185         if (timer == null) {
186             timer = createTimer();
187         }
188         return timer;
189     }
190 
191     /**
192      * Parse the supplied plan.
193      * @param planSource plan source to be parsed
194      * @return parsed plan
195      * @throws ParseException if there is an syntax error in the plan.
196      */
197     private PoshPlan parsePlan(String planSource) throws ParseException {
198         StringReader planReader = new StringReader(planSource);
199         PoshParser parser = new PoshParser(planReader);
200         return parser.parsePlan();
201     }
202 
203     /**
204      * Create one engine for passed @planSrc.
205      *
206      * @param engineId Id of created engine
207      * @param planSrc Source of the plan for the engine.
208      * @throws IllegalArgumentException If @planSrc can't be parsed.
209      */
210     private PoshEngine createEngine(int engineId, String planSrc) {
211         try {
212             PoshPlan plan = parsePlan(planSrc);
213             return new PoshEngine(engineId, plan, getTimer(), bot.getLogger().getCategory(SPOSH_LOG_CATEGORY));
214         } catch (ParseException ex) {
215             bot.getLogger().getCategory(SPOSH_LOG_CATEGORY).log(Level.SEVERE, "Parse exceptions during parsing plan:\n{0}\nStacktrace:\n{1}", new Object[]{planSrc, getStackTrace(ex)});
216             throw new IllegalArgumentException(ex);
217         }
218     }
219 
220     /**
221      * Get engines used by this bot.
222      *
223      * @return Empty list if engine wasn't yet created(they are created in {@link SposhLogicController#initializeController(UT2004Bot)
224      * } ) or the engines.
225      */
226     protected final List<PoshEngine> getEngines() {
227         return engines;
228     }
229 
230     /**
231      * Get all Yaposh plans this bot is supposed to execute. The plans will be
232      * executed in same order as in the returned list. Easiest way is to use
233      * {@link #getPlanFromResource(java.lang.String) }, {@link #getPlanFromFile(java.lang.String)},
234      * {@link #getPlanFromStream(java.io.InputStream) or {@link #getPlansFromDirectory(String)}.
235      * }.
236      *
237      * @return List of sources of the plans
238      */
239     protected abstract List<String> getPlans() throws IOException;
240 
241     /**
242      * Read POSH plan from the stream and return it. Close the stream.
243      * @param in Input stream from which the plan is going to be read
244      * @return Text of the plan, basically content of the stream
245      * @throws IOException If there is some error while reading the stream
246      */
247     protected final String getPlanFromStream(InputStream in) throws IOException {
248         BufferedReader br = new BufferedReader(new InputStreamReader(in));
249 
250         StringBuilder plan = new StringBuilder();
251         String line;
252         try {
253             while ((line = br.readLine()) != null) {
254                 plan.append(line);
255             }
256         } finally {
257             br.close();
258         }
259 
260         return plan.toString();
261     }
262     
263     /**
264      * Reads all '.lap' file from specified directory. They are alphabetically sorted (ascending order).
265      * @param directoryPath
266      * @return
267      * @throws IOException
268      */
269     protected final List<String> getPlansFromDirectory(String directoryPath) throws IOException {
270     	List<File> files = new ArrayList<File>();
271     	for (File file : new File(directoryPath).listFiles()) {
272     		if (".lap".equals(file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(".")))) {
273     			files.add(file);
274     		}
275     	}
276     	Collections.sort(files, new Comparator<File>() {
277 			@Override
278 			public int compare(File o1, File o2) {
279 				return o1.getAbsolutePath().compareTo(o2.getAbsolutePath());
280 			}
281     		
282     	});
283     	List<String> result = new ArrayList<String>();
284     	for (File file : files) {
285     		result.add(getPlanFromFile(file.getAbsolutePath()));
286     	}
287     	return result;
288     }
289 
290     /**
291      * Read POSH plan from the file and return it.
292      * @param filename Path to the file that contains the POSH plan.
293      * @return Text of the plan, basically content of the file
294      * @throws IOException If there is some error while reading the stream
295      */
296     protected final String getPlanFromFile(String filename) throws IOException {
297         FileInputStream f = new FileInputStream(filename);
298         return getPlanFromStream(f);
299     }
300 
301     /**
302      * Get POSh plan from resource int the same jar as the class.
303      * <p>
304      * <pre>
305      *  // Plan is stored in package cz.cuni.amis.pogamut.testbot under name poshPlan.lap
306      *  // This can get the file from .jar or package structure
307      *  getPlanFromResource("cz/cuni/amis/pogamut/testbot/poshPlan.lap");
308      * </pre>
309      * @param resourcePath Path to the plan in some package
310      * @return Content of the plan.
311      * @throws IOException if something goes wrong, like file is missing, hardisk has blown up ect.
312      */
313     protected final String getPlanFromResource(String resourcePath) throws IOException {
314         ClassLoader cl = this.getClass().getClassLoader();
315         return getPlanFromStream(cl.getResourceAsStream(resourcePath));
316     }
317 }