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    }