001 package sysModel.env;
002
003 import controller.IScrollAdapter;
004 import controller.IDisplayAdapter;
005 import lrs.LRStruct;
006 import lrs.visitor.Apply;
007 import lrs.visitor.Clear;
008 import model.ILambda;
009 import sysModel.ICmdFactory;
010 import sysModel.ISecurityAdapter;
011 import sysModel.NoOpLambda;
012 import sysModel.fish.IFishFactory;
013 import sysModel.fish.AFish;
014 import sysModel.fish.FishException;
015 import sysModel.parser.Lexer;
016
017 import java.awt.*;
018 import java.awt.geom.AffineTransform;
019 import java.io.PrintWriter;
020
021 /**
022 * Abstract global environment class.
023 * <p/>
024 * Note: All subclasses must provide a two-parameter constructor that takes in an ICmdFactory for dynamic access to
025 * parse() and makeEnvFactory(), and an ISecurityAdapter.
026 *
027 * @author Mathias Ricken
028 */
029 public abstract class AGlobalEnv {
030 /**
031 * Abstract bsae class of the environment local to a fish.
032 *
033 * @author Mathias Ricken
034 */
035 public abstract class ALocalEnv {
036 /**
037 * State.
038 */
039 ILocalEnvState _state = EmptyLocalEnvState.Singleton;
040
041 /**
042 * Attempt to move the fish forward, which may or may not be successful. The behavior in each case is defined by
043 * the visitor: - If the move cannot be executed, the blockedCmd lambda is applied. The parameter is not used
044 * and set to null. - If the move can be executed, the openCmd lambda is applied. The parameter is an ILambda
045 * that can to be executed to actually move the fish to the target location of this move. The ILambda ignores
046 * the input parameter and returns null.
047 *
048 * @param fish AFish to move
049 * @return return value of lambda executed
050 * @param blockedCmd lambda to apply if blocked
051 * @param openCmd lambda to apply if open
052 */
053 public Object tryMoveFwd(AFish fish, final IBlockedCommand blockedCmd, final IOpenCommand openCmd) {
054 // TODO: PART3
055 return null;
056 }
057
058 /**
059 * Attempt to breed the fish forward, which may or may not be successful. The behavior in each case is defined
060 * by the visitor: - If the breeding cannot be executed, the blockedCmd lambda is applied. The parameter is not
061 * used and set to null. - If the breeding can be executed, the openCmd lambda is applied. The parameter is an
062 * ILambda that can to be executed to actually move the fish to the target location of this breeding. The
063 * ILambda ignores the input parameter and returns null.
064 *
065 * @param fish AFish to move
066 * @return return value of lambda executed
067 * @param blockedCmd lambda to apply if blocked
068 * @param openCmd lambda to apply if open
069 */
070 public Object tryBreedFwd(final AFish fish, final IBlockedCommand blockedCmd, final IOpenCommand openCmd) {
071 // TODO: PART3
072 return null;
073 }
074
075 /**
076 * Draw the fish on the graphics object. The graphics object still has to be translated and rotated properly,
077 *
078 * @param fish AFish to drawFish
079 * @param g graphics object to drawFish on
080 * @param comp component to drawFish on
081 */
082 public abstract void drawFish(AFish fish, Graphics2D g, Component comp);
083
084 /**
085 * Turn the fish radians to the right.
086 *
087 * @param fish AFish to turn
088 * @param radians radians to turn
089 */
090 public abstract void turnRight(AFish fish, double radians);
091
092 /**
093 * Remove the fish from the environment.
094 *
095 * @param fish AFish to remove
096 */
097 public void removeFish(AFish fish) {
098 ILambda deleteLambda = AGlobalEnv.this.removeFish(this);
099
100 // delete the fish from the simulation by executing the deleteLambda
101 deleteLambda.apply(null);
102 }
103
104 /**
105 * Execute a visitor on this local environment.
106 *
107 * @param visitor visitor to execute
108 * @param param visitor-specific parameter
109 * @return visitor-specific return value
110 */
111 public Object execute(ILocalEnvVisitor visitor, Object param) {
112 return _state.execute(this, visitor, param);
113 }
114
115 /**
116 * Set state.
117 *
118 * @param state new state
119 */
120 public void setState(ILocalEnvState state) {
121 _state = state;
122 }
123
124 /**
125 * Factory method for a move lambda.
126 *
127 * @param le local environment for the target
128 * @return move lambda to execute the move to the target
129 */
130 protected abstract ILambda makeMoveLambda(ALocalEnv le);
131
132 /**
133 * Make local environment in forward direction. Do not block yourself.
134 *
135 * @return new local environment in forward direction
136 */
137 protected abstract ALocalEnv makeMoveFwdLocalEnv();
138 }
139
140 /**
141 * Lambda to execute a breed.
142 */
143 protected class BreedLambda implements ILambda {
144 /// AFish to breed/
145 private final AFish _fish;
146 /// Target location.
147 private final ALocalEnv _newLocalEnv;
148
149 /**
150 * Constructor.
151 *
152 * @param fish fish to breed
153 * @param newLocalEnv target location
154 */
155 public BreedLambda(AFish fish, ALocalEnv newLocalEnv) {
156 _fish = fish;
157 _newLocalEnv = newLocalEnv;
158 }
159
160 /**
161 * Execute the breeding.
162 *
163 * @param param not used
164 * @return null
165 */
166 public Object apply(Object param) {
167 // execute the breeding
168 AFish clone = (AFish) _fish.clone();
169 ILambda addLambda = addFish(_newLocalEnv, clone);
170
171 // add the fish to the simulation by executing the addLambda
172 addLambda.apply(null);
173
174 // deactivate all lambdas
175 deactivateBreedLambdas();
176 return null;
177 }
178 }
179
180 /**
181 * Color of the ocean.
182 */
183 protected static final Color OCEAN_BLUE = new Color(75, 75, 255);
184
185 /**
186 * List of move commands issued. The commands issued in a fish's step are recorded so that they can be deactivated
187 * once a fish completes its step. This prevents a fish class from saving commands and executing them at a later
188 * time.
189 */
190 protected LRStruct _moveLambdas;
191
192 /**
193 * List of breed commands issued. The commands issued in a fish's step are recorded so that they can be deactivated
194 * once a fish completes its step. This prevents a fish class from saving commands and executing them at a later
195 * time.
196 */
197 protected LRStruct _breedLambdas;
198
199 /**
200 * Command factory for lambdas given to the simulation driver.
201 */
202 protected ICmdFactory _cmdFactory;
203
204 /**
205 * Security manager that controls the actions of the fish.
206 */
207 protected ISecurityAdapter _securityAdapter;
208
209 /**
210 * Color of the water.
211 */
212 protected Color _waterColor = OCEAN_BLUE;
213
214 /**
215 * Visitor to opererate on a local environment. Note: should be protected, but is public for testing purposes
216 */
217 public interface ILocalEnvVisitor {
218 /**
219 * Case called if the location is empty.
220 *
221 * @param host the location
222 * @param param visitor-specific parameter
223 * @return visitor-specific return value
224 */
225 public abstract Object emptyCase(ALocalEnv host, Object param);
226
227 /**
228 * Case called if the location is non-empty.
229 *
230 * @param host the location
231 * @param param visitor-specific parameter
232 * @return visitor-specific return value
233 */
234 public abstract Object nonEmptyCase(ALocalEnv host, Object param);
235 }
236
237 /**
238 * Construct a new abstract global environment.
239 *
240 * @param cmdFactory command factory to use
241 * @param sm security manager to control fish actions
242 */
243 public AGlobalEnv(ICmdFactory cmdFactory, ISecurityAdapter sm) {
244 _breedLambdas = new LRStruct();
245 _moveLambdas = new LRStruct();
246 _cmdFactory = cmdFactory;
247 _securityAdapter = sm;
248 }
249
250 /**
251 * Deactivate move commands and clear list. If this is called after a fish has moved or after a step is complete, it
252 * makes it impossible to use the commands at a later time.
253 */
254 protected void deactivateMoveLambdas() {
255 _moveLambdas.execute(Apply.Singleton, new ILambda() {
256 /**
257 * Execute command.
258 */
259 public Object apply(Object param) {
260 DeactivatableLambda cmd = (DeactivatableLambda) param;
261 cmd.deactivate();
262 return null;
263 }
264 });
265 _moveLambdas.execute(Clear.Singleton, null);
266 }
267
268 /**
269 * Deactivate move commands and clear list. If this is called after a fish has moved or after a step is complete, it
270 * makes it impossible to use the commands at a later time.
271 */
272 protected void deactivateBreedLambdas() {
273 _breedLambdas.execute(Apply.Singleton, new ILambda() {
274 /**
275 * Execute command.
276 */
277 public Object apply(Object param) {
278 DeactivatableLambda cmd = (DeactivatableLambda) param;
279 cmd.deactivate();
280 return null;
281 }
282 });
283 _breedLambdas.execute(Clear.Singleton, null);
284 }
285
286 /**
287 * Edit a location.
288 *
289 * @param p position
290 * @param fishFactory factory to make a new fish
291 * @param button
292 * @return the lambda that the simulation control should execute for this action
293 */
294 public ILambda makeEditCmd(Point.Double p, final IFishFactory fishFactory, final int button) {
295 final ALocalEnv localEnv = makeLocalEnv(p);
296 return (ILambda) localEnv.execute(new ILocalEnvVisitor() {
297 public Object emptyCase(ALocalEnv host, Object param) {
298 // location is empty, add fish
299 try {
300 return addFish(localEnv, fishFactory.createFish());
301 }
302 catch (Exception e) {
303 System.out.println("Factory throws: " + e);
304 e.printStackTrace();
305 return NoOpLambda.instance();
306 }
307 }
308
309 public Object nonEmptyCase(ALocalEnv host, Object param) {
310 // location is not empty
311 return editFish(host, fishFactory, button);
312 }
313 }, null);
314 }
315
316 /**
317 * Add the fish to the global environment.
318 *
319 * @param localEnv local environment
320 * @param fish fish to add
321 * @return add lambda
322 */
323 protected ILambda addFish(ALocalEnv localEnv, AFish fish) {
324 // prepare the fish
325 localEnv.setState(NonEmptyLocalEnvState.Singleton);
326 fish.setLocalEnvironment(localEnv);
327
328 // add the fish to the environment's data
329 addFishToInternalData(localEnv, fish);
330
331 // set up the lambda to make the simulation control add the fish to the engine
332 return _cmdFactory.makeAddCmd(fish);
333 }
334
335 /**
336 * Remove the fish from the global environment.
337 *
338 * @param localEnv local environment
339 * @return delete lambda
340 */
341 protected ILambda removeFish(ALocalEnv localEnv) {
342 // change the state of the local environment
343 localEnv.setState(EmptyLocalEnvState.Singleton);
344
345 // remove the fish to the environment's data
346 removeFishFromInternalData(localEnv);
347
348 // set up the lambda to make the simulation control delete the fish from the engine
349 return _cmdFactory.makeDeleteCmd(localEnv);
350 }
351
352 /**
353 * Draw the environment in the region requested in model coordinates.
354 *
355 * @param g2 the Graphics2D object to use to render
356 * @param comp the component to makeDrawCmd on
357 * @param p1 top left corner
358 * @param p2 bottom right corner
359 * @return lambda for the simulation control to execute
360 */
361 public ILambda makeDrawCmd(final Graphics2D g2, final Component comp, final Point.Double p1, Point.Double p2) {
362 // makeDrawCmd background
363 drawBackground(g2, p1, p2);
364
365 // tell the simulation control to makeDrawCmd all fish
366 return _cmdFactory.makeNotifyCmd(new ILambda() {
367 public Object apply(Object param) {
368 // save the old transform
369 AffineTransform oldTransform = g2.getTransform();
370
371 // trams;ate view for drawing fish
372 g2.translate(-(int) Math.floor(p1.x), -(int) Math.floor(p1.y));
373
374 // makeDrawCmd the fish
375 ((FishApplyParams) param).fish().draw(g2, comp);
376
377 // change the old transform
378 g2.setTransform(oldTransform);
379 return null;
380 }
381 });
382 }
383
384 /**
385 * Draw ocean background.
386 *
387 * @param g2 graphics object
388 * @param p1 top left corner
389 * @param p2 bottom right corner
390 */
391 protected void drawBackground(Graphics2D g2, Point.Double p1, Point.Double p2) {
392 g2.setColor(_waterColor); // fill blue background
393 g2.fillRect(0, 0, (int) (p2.x - p1.x), (int) (p2.y - p1.y));
394
395 // makeDrawCmd grid lines
396 g2.setColor(Color.black);
397 for (int y = 0; y <= (p2.y - p1.y); y++) {
398 g2.drawLine(0, y, (int) (p2.x - p1.x), y);
399 }
400 for (int x = 0; x <= (p2.x - p1.x); x++) {
401 g2.drawLine(x, 0, x, (int) (p2.y - p1.y));
402 }
403 }
404
405 /**
406 * Make the step command for the simulation driver to execute.
407 *
408 * @return step command
409 */
410 public ILambda makeStepCmd() {
411 return _cmdFactory.makeNotifyCmd(new ILambda() {
412 public Object apply(final Object param) {
413 deactivateBreedLambdas();
414 deactivateMoveLambdas();
415 try {
416 Thread thread;
417 final RuntimeException potentialInternalException = new RuntimeException("Fish thread threw exception");
418 final DeactivatableLambda internalExceptionLambda = new DeactivatableLambda(new ILambda() {
419 public Object apply(Object param) {
420 throw potentialInternalException;
421 }
422 });
423 final RuntimeException potentialTimeOutException = new RuntimeException("Fish step timed out");
424 final DeactivatableLambda timeOutExceptionLambda = new DeactivatableLambda(new ILambda() {
425 @SuppressWarnings("deprecation")
426 public Object apply(Object param) {
427 _securityAdapter.setProtected(false);
428 // NOTE: Java issues a warning that the use of Thread.stop() is deprecated.
429 // This is the only way to kill a fish with divergent behavior, though.
430 // Please ignore this warning.
431 ((Thread)param).stop();
432 throw potentialTimeOutException;
433 }
434 });
435 thread = new Thread(_securityAdapter.getFishThreadGroup(), new Runnable() {
436 public void run() {
437 _securityAdapter.setProtected(true);
438 try {
439 ((FishApplyParams) param).fish().act();
440 }
441 finally {
442 _securityAdapter.setProtected(false);
443 timeOutExceptionLambda.deactivate();
444 }
445 internalExceptionLambda.deactivate();
446 }
447 });
448 thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
449 public void uncaughtException(Thread t, Throwable e) {
450 potentialInternalException.initCause(e);
451 }
452 });
453 thread.start();
454 thread.join(5000);
455 timeOutExceptionLambda.apply(thread);
456 internalExceptionLambda.apply(null);
457 }
458 catch (Throwable t) {
459 // since the fish is going to die anyway, it can't use the lambdas anyway
460 // we don't have to deactivate the lambdas
461 _breedLambdas = new LRStruct();
462 _moveLambdas = new LRStruct();
463
464 Throwable d = t;
465 while ((null != d)&&(null != d.getCause())) {
466 d = d.getCause();
467 }
468 _securityAdapter.handleException(d);
469
470 throw new FishException(t);
471 }
472
473 return null;
474 }
475 });
476 }
477
478 /**
479 * Save an environment to a file.
480 *
481 * @param pw PrintWriter to use for output
482 * @return lambda for the simulation control to execute
483 */
484 public ILambda save(final PrintWriter pw) {
485 // print header
486 printHeader(pw);
487
488 // tell the simulation control to to save all the fish
489 return new ILambda() {
490 public Object apply(Object param) {
491 pw.println(param);
492 return null;
493 }
494 };
495 }
496
497 /**
498 * Factory method for parsing a stream of tokens and creating a global environment from it.
499 *
500 * @param l lexer to use
501 * @return new global environment
502 */
503 public AGlobalEnv parse(Lexer l) {
504 AGlobalEnv env = parseEnvironment(l);
505 env.parseFish(l);
506 return env;
507 }
508
509 /* Abstract methods below. */
510
511 /**
512 * Edit the fish.
513 *
514 * @param localEnv local environment
515 * @param fishFactory
516 * @param button
517 * @return lambda for the simulation control to execute
518 */
519 protected abstract ILambda editFish(ALocalEnv localEnv, IFishFactory fishFactory, int button);
520
521 /**
522 * Add the fish to the environment's internal data structure.
523 *
524 * @param localEnv local environment
525 * @param fish fish to add
526 */
527 protected abstract void addFishToInternalData(ALocalEnv localEnv, AFish fish);
528
529 /**
530 * Remove the fish to the environment's internal data structure.
531 *
532 * @param localEnv local environment
533 */
534 protected abstract void removeFishFromInternalData(ALocalEnv localEnv);
535
536 /**
537 * Factory method for parsing the parameters for an environment and then creating it.
538 *
539 * @param l lexer
540 * @return new global environment
541 */
542 protected abstract AGlobalEnv parseEnvironment(Lexer l);
543
544 /**
545 * Parse fish and add them to the environment.
546 *
547 * @param l parser to read from
548 */
549 protected abstract void parseFish(Lexer l);
550
551 /**
552 * Create a local environment for the position.
553 *
554 * @param p position
555 * @return local environment
556 */
557 protected abstract ALocalEnv makeLocalEnv(Point.Double p);
558
559 /**
560 * Create the environment settings class for an environment. Factory method.
561 *
562 * @return environment settings class
563 */
564 public abstract AEnvFactory makeEnvFactory();
565
566 /**
567 * Print file header.
568 *
569 * @param pw PrintWriter to use
570 */
571 protected abstract void printHeader(PrintWriter pw);
572
573 /**
574 * Get size of the display.
575 *
576 * @return size of the display in model coordinate units.
577 */
578 public abstract Dimension getDisplaySize();
579
580 /**
581 * The action to be executed if the display should return home.
582 *
583 * @param sa scroll adapter
584 */
585 public abstract void returnHome(IScrollAdapter sa);
586
587 /**
588 * Ask the model where to scroll, given where the user has scrolled. If the environment just acts like a normal
589 * panal, it should return pos without modification. If the environment recenters, it should return a position in
590 * the middle of the pan area. All coordinates are in model coordinate units.
591 *
592 * @param pos position where the user scrolled to
593 * @return position where the environment wants the view to be
594 * @see IDisplayAdapter#getPanDelta
595 */
596 public abstract Point.Double getViewPosition(Point.Double pos);
597
598 /**
599 * Ask the model how much to pan, given where the user scrolled. If the environment just acts like a normal panal,
600 * it should return (0,0). If the environment recenters, it should return delta without modification. All
601 * coordinates are in model coordinate units.
602 *
603 * @param delta how far the user scrolled
604 * @return how far the panel should scroll
605 * @see IDisplayAdapter#getViewPosition
606 */
607 public abstract Point.Double getPanDelta(Point.Double delta);
608
609 /**
610 * Get a tool tip description for a specific place in the environment.
611 *
612 * @param p mouse coordinates
613 * @return lambda for the simulation controller to execute. Must return the tooltip string.
614 */
615 public abstract ILambda getToolTipText(Point.Double p);
616 }