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