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 }