001    package sysModel.env;
002    
003    import controller.IScrollAdapter;
004    import controller.IDisplayAdapter;
005    import junit.framework.TestCase;
006    import lrs.IAlgo;
007    import lrs.LRStruct;
008    import lrs.visitor.GetLength;
009    import lrs.visitor.Remove;
010    import model.ILambda;
011    import model.RandNumGenerator;
012    import model.fish.GenericFish;
013    import sysModel.ICmdFactory;
014    import sysModel.ISecurityAdapter;
015    import sysModel.NoOpLambda;
016    import sysModel.fish.IFishFactory;
017    import sysModel.fish.AFish;
018    import sysModel.parser.DefaultTokenVisitor;
019    import sysModel.parser.Lexer;
020    import sysModel.parser.ParserException;
021    
022    import javax.swing.*;
023    import java.awt.*;
024    import java.awt.event.MouseEvent;
025    import java.awt.geom.AffineTransform;
026    import java.lang.reflect.Constructor;
027    import java.util.LinkedList;
028    import java.util.Random;
029    import java.io.PrintWriter;
030    
031    /**
032     * An environment that does not use grids to place fish.
033     *
034     * @author Mathias Ricken
035     */
036    public class NoGridEnv extends AGlobalEnv {
037        /**
038         * Concrete direction class.
039         */
040        public static class Direction {
041            /// floating point epsilon for comparisons
042            private static double EPSILON = 1e-10;
043    
044            /**
045             * Direction delta x.
046             */
047            private double _dx;
048    
049            /**
050             * Direction delta y.
051             */
052            private double _dy;
053    
054            /**
055             * Return the direction delta x.
056             *
057             * @return double direction delta x
058             */
059            public double getDeltaX() {
060                return _dx;
061            }
062    
063            /**
064             * Return the direction delta y.
065             *
066             * @return double direction delta y
067             */
068            public double getDeltaY() {
069                return _dy;
070            }
071    
072            /**
073             * Return a new object which has the same direction.
074             *
075             * @return new object with same direction
076             */
077            public Direction duplicate() {
078                return new Direction(_dx, _dy);
079            }
080    
081            /**
082             * Reverse this direction.
083             */
084            public void reverseDirection() {
085                _dx = -_dx;
086                _dy = -_dy;
087            }
088    
089            /**
090             * Return true of this direction is the same as the other.
091             *
092             * @param other other direction
093             * @return true if the directions are the same
094             */
095            public boolean same(Direction other) {
096                double diffx = _dx - other.getDeltaX();
097                double diffy = _dy - other.getDeltaY();
098                return (Math.abs(diffx) < EPSILON && Math.abs(diffy) < EPSILON);
099            }
100    
101            /**
102             * Turn this direction to the left.
103             *
104             * @param radians radians to turn
105             */
106            public void turnLeft(double radians) {
107                turnRight(-radians);
108            }
109    
110            /**
111             * Turn this direction PI/2 radians to the left.
112             */
113            public void turnLeft() {
114                turnLeft(Math.PI / 2);
115            }
116    
117            /**
118             * Turn this direction to the right.
119             *
120             * @param radians radians to turn
121             */
122            public void turnRight(double radians) {
123                double dx = _dx * Math.cos(radians) - _dy * Math.sin(radians);
124                double dy = _dx * Math.sin(radians) + _dy * Math.cos(radians);
125                _dx = dx;
126                _dy = dy;
127            }
128    
129            /**
130             * Turn this direction PI/2 radians to the right.
131             */
132            public void turnRight() {
133                turnRight(Math.PI / 2);
134            }
135    
136            /**
137             * Constructor.
138             *
139             * @param dx delta x
140             * @param dy delta y
141             */
142            public Direction(double dx, double dy) {
143                _dx = dx;
144                _dy = dy;
145            }
146    
147            /**
148             * Copy constructor.
149             *
150             * @param other other direction
151             */
152            public Direction(Direction other) {
153                _dx = other.getDeltaX();
154                _dy = other.getDeltaY();
155            }
156    
157            /**
158             * Creates a new direction facing north.
159             */
160            public Direction() {
161                _dx = 0;
162                _dy = -1;
163            }
164    
165            /**
166             * Overridden toString method.
167             *
168             * @return string representation
169             */
170            public String toString() {
171                return "(" + _dx + ", " + _dy + ')';
172            }
173    
174            /**
175             * Parses a direction.
176             *
177             * @param l parser to read from
178             * @return parsed direction
179             */
180            public static Direction parse(final Lexer l) {
181                // read (
182                return (Direction) l.nextToken().execute(new DefaultTokenVisitor() {
183                    public Object defaultCase() {
184                        throw new ParserException("Invalid token");
185                    }
186    
187                    public Object openCase() {
188                        // read x
189                        return l.nextToken().execute(new DefaultTokenVisitor() {
190                            public Object defaultCase() {
191                                throw new ParserException("Invalid token");
192                            }
193    
194                            public Object numCase(final double x) {
195                                // read ,
196                                return l.nextToken().execute(new DefaultTokenVisitor() {
197                                    public Object defaultCase() {
198                                        throw new ParserException("Invalid token");
199                                    }
200    
201                                    public Object commaCase() {
202                                        // read y
203                                        return l.nextToken().execute(new DefaultTokenVisitor() {
204                                            public Object defaultCase() {
205                                                throw new ParserException("Invalid token");
206                                            }
207    
208                                            public Object numCase(final double y) {
209                                                // read )
210                                                return l.nextToken().execute(new DefaultTokenVisitor() {
211                                                    public Object defaultCase() {
212                                                        throw new ParserException("Invalid token");
213                                                    }
214    
215                                                    public Object closeCase() {
216                                                        return new Direction(x, y);
217                                                    }
218                                                });
219                                            }
220                                        });
221                                    }
222                                });
223                            }
224                        });
225                    }
226                });
227            }
228    
229            /**
230             * Rotate the supplied Graphics2D object to match this direction.
231             *
232             * @param g graphics object to rotate
233             */
234            public void rotateGraphics(Graphics2D g) {
235                /*
236                Find the angle between the current direction dir and (1,0) in a clockwise direction.
237                dir . (0, -1) = |dir|*|(0,-1)|*cos(theta). But |dir| = |(0, -1)| = 1, so
238                cos(theta) = dir . (0, -1) = -dir.y
239                This is always the smaller angle, though. Therefore
240                theta = arccos(-dir.y)        if dir.x >= 0
241                      = 2 PI - arccos(-dir.y) if dir.x < 0
242                */
243                double theta = getAngle();
244    
245                if (null != g) {
246                    g.rotate(theta);
247                }
248            }
249    
250            /**
251             * Return the angle between (0,-1) and this direction.
252             *
253             * @return angle in radians
254             */
255            public double getAngle() {
256                // keeping argument to acos in range [-1,1] because acos produced a NaN on one system
257                double theta = Math.acos(Math.max(-1,Math.min(1,-_dy)));
258                if (0 > _dx) {
259                    theta = 2 * Math.PI - theta;
260                }
261                return theta;
262            }
263        }
264    
265        /**
266         * Concrete location class.
267         *
268         * @author Mathias G. Ricken
269         */
270        public static class Location {
271            /**
272             * Column.
273             */
274            private double _x;
275    
276            /**
277             * Row.
278             */
279            private double _y;
280    
281            /**
282             * Return column.
283             *
284             * @return double column
285             */
286            public double getX() {
287                return _x;
288            }
289    
290            /**
291             * Return row.
292             *
293             * @return double row
294             */
295            public double getY() {
296                return _y;
297            }
298    
299            /**
300             * Set column.
301             *
302             * @param _x New column
303             */
304            public void setX(double _x) {
305                this._x = _x;
306            }
307    
308            /**
309             * Set row.
310             *
311             * @param _y New row
312             */
313            public void setY(double _y) {
314                this._y = _y;
315            }
316    
317            /**
318             * Constructor.
319             *
320             * @param x column
321             * @param y row
322             */
323            public Location(double x, double y) {
324                _x = x;
325                _y = y;
326            }
327    
328            /**
329             * Return true of this location is the same as the other.
330             *
331             * @param other other location
332             * @return true if the locations are the same
333             */
334            public boolean same(Location other) {
335                return (_x == other.getX()) &&
336                        (_y == other.getY());
337            }
338    
339            /**
340             * Return true of the other location is in a square with this location at the center, facing in the given
341             * direction, with sides of the specified length.
342             *
343             * @param other   location to compare to
344             * @param forward direction the square is facing (this direction is perpendicular to two of the side walls)
345             * @param side    side length
346             * @return true if the other point is inside
347             */
348            public boolean inSquare(Location other, Direction forward, double side) {
349                // distante center-to-other
350                double deltaX = other.getX() - _x;
351                double deltaY = other.getY() - _y;
352    
353                // point at center front of square
354                double frontX = forward.getDeltaX();
355                double frontY = forward.getDeltaY();
356    
357                // project center-to-other onto center-to-front
358                double toFront = deltaX * frontX + deltaY * frontY;
359                if (Math.abs(toFront) > side / 2) {
360                    // beyond front or back side
361                    return false;
362                }
363    
364                // point at center right of square
365                double rightX = -forward.getDeltaY();
366                double rightY = forward.getDeltaX();
367    
368                // project center-to-other onto center-to-right
369                double toRight = deltaX * rightX + deltaY * rightY;
370                return Math.abs(toRight) <= side / 2;
371    
372            }
373    
374            /**
375             * Return the location of a neighbor in the given direction.
376             *
377             * @param dir the direction of the neighbor to be returned
378             * @return neighbor in that direction
379             */
380            public Location getNeighbor(Direction dir) {
381                return new Location(_x + dir.getDeltaX(),
382                                    _y + dir.getDeltaY());
383            }
384    
385            /**
386             * Overridden toString method.
387             *
388             * @return string representation
389             */
390            public String toString() {
391                return "(" + _x + ", " + _y + ')';
392            }
393    
394            /**
395             * Parses a location.
396             *
397             * @param l parser to read from
398             * @return parsed location
399             */
400            public static Location parse(final Lexer l) {
401                // read (
402                return (Location) l.nextToken().execute(new DefaultTokenVisitor() {
403                    public Object defaultCase() {
404                        throw new ParserException("Invalid token");
405                    }
406    
407                    public Object openCase() {
408                        // read x
409                        return l.nextToken().execute(new DefaultTokenVisitor() {
410                            public Object defaultCase() {
411                                throw new ParserException("Invalid token");
412                            }
413    
414                            public Object numCase(final double x) {
415                                // read ,
416                                return l.nextToken().execute(new DefaultTokenVisitor() {
417                                    public Object defaultCase() {
418                                        throw new ParserException("Invalid token");
419                                    }
420    
421                                    public Object commaCase() {
422                                        // read y
423                                        return l.nextToken().execute(new DefaultTokenVisitor() {
424                                            public Object defaultCase() {
425                                                throw new ParserException("Invalid token");
426                                            }
427    
428                                            public Object numCase(final double y) {
429                                                // read )
430                                                return l.nextToken().execute(new DefaultTokenVisitor() {
431                                                    public Object defaultCase() {
432                                                        throw new ParserException("Invalid token");
433                                                    }
434    
435                                                    public Object closeCase() {
436                                                        return new Location(x, y);
437                                                    }
438                                                });
439                                            }
440                                        });
441                                    }
442                                });
443                            }
444                        });
445                    }
446                });
447            }
448        }
449    
450        /**
451         * Concrete local environment for the square unbounded environment.
452         */
453        protected class LocalEnvironment extends ALocalEnv {
454            /**
455             * Location.
456             */
457            Location _loc;
458    
459            /**
460             * Direction.
461             */
462            Direction _dir;
463    
464            /**
465             * State.
466             */
467            ILocalEnvState _state = EmptyLocalEnvState.Singleton;
468    
469            /**
470             * Lambda to execute a move.
471             */
472            private class MoveLambda implements ILambda {
473                /// target direction
474                private Direction _newDir;
475                /// target location
476                private Location _newLoc;
477    
478                /**
479                 * Constructor.
480                 *
481                 * @param le target local environment
482                 */
483                public MoveLambda(LocalEnvironment le) {
484                    _newLoc = new Location(le._loc.getX(), le._loc.getY());
485                    _newDir = new Direction(le._dir);
486                }
487    
488                /**
489                 * Execute the move.
490                 *
491                 * @param param not used
492                 * @return null
493                 */
494                public Object apply(Object param) {
495                    // execute the movement
496                    _loc = _newLoc;
497                    _dir = _newDir;
498    
499                    // deactivate all lambdas
500                    deactivateMoveLambdas();
501                    return null;
502                }
503            }
504    
505            /**
506             * Construct a new local environment.
507             *
508             * @param loc location
509             * @param dir direction
510             */
511            public LocalEnvironment(Location loc, Direction dir) {
512                _loc = loc;
513                _dir = dir;
514            }
515    
516            /**
517             * Accessor for the location.
518             *
519             * @return location
520             */
521            public Location location() {
522                return _loc;
523            }
524    
525            /**
526             * Accessor for the direction.
527             *
528             * @return direction
529             */
530            public Direction direction() {
531                return _dir;
532            }
533    
534            /**
535             * Make local environment in forward direction. Do not block yourself.
536             *
537             * @return new local environment in forward direction
538             */
539            protected ALocalEnv makeMoveFwdLocalEnv() {
540                // remove this local environment to prevent collision with itself
541                _localEnvList.execute(Remove.Singleton, this);
542                ALocalEnv le = makeLocalEnv(_loc.getNeighbor(_dir), _dir);
543                // add this local environment back in
544                _localEnvList.insertFront(this);
545                return le;
546            }
547    
548            /**
549             * Factory method for a move lambda.
550             *
551             * @param le local environment for the target
552             * @return move lambda to execute the move to the target
553             */
554            protected ILambda makeMoveLambda(ALocalEnv le) {
555                return new MoveLambda((LocalEnvironment) le);
556            }
557    
558            /**
559             * Draw the fish on the graphics object. The graphics object still has to be translated and rotated properly,
560             *
561             * @param fish AFish to drawFish
562             * @param g    graphics object to drawFish on
563             * @param comp component to drawFish on
564             */
565            public void drawFish(AFish fish, Graphics2D g, Component comp) {
566                double centerX = _loc.getX();
567                double centerY = _loc.getY();
568    
569                // save transformation
570                AffineTransform oldTransform = g.getTransform();
571                // translate to center of field
572                g.translate(centerX, centerY);
573    
574                // set up the correct rotation
575                _dir.rotateGraphics(g);
576    
577                // makeDrawCmd the fish
578                fish.paint(g, comp);
579    
580                // restore transformation
581                g.setTransform(oldTransform);
582            }
583    
584            /**
585             * Turn the fish radians to the right.
586             *
587             * @param fish    AFish to turn
588             * @param radians radians to turn
589             */
590            public void turnRight(AFish fish, double radians) {
591                _dir.turnRight(radians);
592            }
593    
594            /**
595             * String representation of the local environment. Should be "(x, y) (dx, dy)".
596             *
597             * @return string representation
598             */
599            public String toString() {
600                return _loc.toString() + ' ' + _dir.toString();
601            }
602        }
603    
604        private static int PAN_SIZE = 2000;
605        private static Point.Double PAN_CENTER = new Point.Double(PAN_SIZE / 2, PAN_SIZE / 2);
606    
607        /**
608         * List of local environments in this global environment.
609         */
610        private LRStruct _localEnvList;
611    
612        /**
613         * Number of steps for complete rotation.
614         */
615        private int _rotSteps;
616    
617        /**
618         * Create a new environment without grids. Note: This constructor needs to exist and be public for the "environment
619         * selection" dialog to work.
620         *
621         * @param cmdFactory command factory to use
622         * @param sm         security manager to control fish actions
623         */
624        public NoGridEnv(ICmdFactory cmdFactory, ISecurityAdapter sm) {
625            super(cmdFactory, sm);
626            _localEnvList = new LRStruct();
627        }
628    
629        /**
630         * Create a new environment without grids.
631         *
632         * @param cmdFactory command factory to use
633         * @param sm         security manager to control fish actions
634         * @param rotSteps   rotation steps
635         * @param waterColor color of the water
636         */
637        public NoGridEnv(ICmdFactory cmdFactory, ISecurityAdapter sm, int rotSteps, Color waterColor) {
638            super(cmdFactory, sm);
639            _localEnvList = new LRStruct();
640            _rotSteps = rotSteps;
641            _waterColor = waterColor;
642        }
643    
644        /**
645         * Add the fish to the global environment.
646         *
647         * @param localEnv local environment
648         * @param fish     fish to add
649         */
650        protected void addFishToInternalData(ALocalEnv localEnv, AFish fish) {
651            _localEnvList.insertFront(localEnv);
652        }
653    
654        /**
655         * Remove the fish from the global environment.
656         *
657         * @param localEnv local environment
658         */
659        protected void removeFishFromInternalData(ALocalEnv localEnv) {
660            _localEnvList.execute(Remove.Singleton, localEnv);
661        }
662    
663        /**
664         * Create a local environment for the position.
665         *
666         * @param p position
667         * @return local environment
668         */
669        public ALocalEnv makeLocalEnv(Point.Double p) {
670            return makeLocalEnv(new Location(p.getX(), p.getY()), new Direction());
671        }
672    
673        /**
674         * Create a local environment for the position.
675         *
676         * @param loc location
677         * @param dir direction
678         * @return local environment
679         */
680        private ALocalEnv makeLocalEnv(final Location loc, final Direction dir) {
681            return (ALocalEnv) _localEnvList.execute(new IAlgo() {
682                /**
683                 * Operates on a non-empty LRStruct host, given an input object.
684                 *
685                 * @param host a non-empty LRStruct.
686                 * @param inp  input object needed by this IAlgo.
687                 * @return an appropriate output object.
688                 */
689                public Object nonEmptyCase(LRStruct host, Object inp) {
690                    LocalEnvironment localEnv = (LocalEnvironment) host.getFirst();
691                    if (localEnv.location().inSquare(loc, localEnv.direction(), 1)) {
692                        return localEnv;
693                    }
694                    else {
695                        return host.getRest().execute(this, inp);
696                    }
697                }
698    
699                /**
700                 * Operates on an empty LRStruct host, given an input object.
701                 *
702                 * @param host an empty LRStruct.
703                 * @param inp  input object needed by this IAlgo.
704                 * @return an appropriate output object.
705                 */
706                public Object emptyCase(LRStruct host, Object inp) {
707                    return new LocalEnvironment(loc, dir);
708                }
709            }, null);
710        }
711    
712        /**
713         * Create a local environment with the given data.
714         *
715         * @param loc location
716         * @param dir direction
717         * @return new local environment
718         */
719        protected LocalEnvironment createLocalEnvironment(Location loc, Direction dir) {
720            return new LocalEnvironment(loc, dir);
721        }
722    
723        /**
724         * Edit the fish.
725         *
726         * @param le          local environment
727         * @param fishFactory
728         * @param button
729         * @return lambda to edit the fish
730         */
731        public ILambda editFish(ALocalEnv le, final IFishFactory fishFactory, int button) {
732            // by default, the control does not need to do anything
733            ILambda lambdaForControl = NoOpLambda.instance();
734    
735            // rotate the fish
736            final LocalEnvironment localEnv = (LocalEnvironment) le;
737    
738            double initialAngle = localEnv.direction().getAngle();
739            localEnv.direction().turnRight(2 * Math.PI / _rotSteps);
740    
741            // check if the fish should be removed
742            if (initialAngle > localEnv.direction().getAngle()) {
743                // rotated back to initial position, remove
744    
745                // set up lambda for simulation control
746                lambdaForControl = _cmdFactory.makeDeleteCmd(localEnv);
747    
748                // and remove it from the environment's data
749                ILambda deleteLambda = removeFish(le);
750    
751                // delete the fish from the simulation by executing the deleteLambda
752                deleteLambda.apply(null);
753            }
754    
755            return lambdaForControl;
756        }
757    
758        /**
759         * Factory method for parsing a stream of tokens and creating a global environment from it.
760         *
761         * @param l lexer to use
762         * @return new global environment
763         */
764        protected AGlobalEnv parseEnvironment(final Lexer l) {
765            // have to read number of steps
766            return (AGlobalEnv) l.nextToken().execute(new DefaultTokenVisitor() {
767                public Object defaultCase() {
768                    throw new ParserException("Invalid token");
769                }
770    
771                public Object numCase(final double rotSteps) {
772                    // rotation steps was read
773                    return (AGlobalEnv) l.nextToken().execute(new DefaultTokenVisitor() {
774                        public Object defaultCase() {
775                            throw new ParserException("Invalid token");
776                        }
777    
778                        public Object numCase(final double red) {
779                            // red was read
780                            return (AGlobalEnv) l.nextToken().execute(new DefaultTokenVisitor() {
781                                public Object defaultCase() {
782                                    throw new ParserException("Invalid token");
783                                }
784    
785                                public Object numCase(final double green) {
786                                    // green was read
787                                    return (AGlobalEnv) l.nextToken().execute(new DefaultTokenVisitor() {
788                                        public Object defaultCase() {
789                                            throw new ParserException("Invalid token");
790                                        }
791    
792                                        public Object numCase(double blue) {
793                                            // blue was read
794                                            NoGridEnv env = new NoGridEnv(_cmdFactory, _securityAdapter, (int) rotSteps,
795                                                                          new Color((int) red, (int) green, (int) blue));
796    
797                                            // parse fish
798                                            env.parseFish(l);
799    
800                                            return env;
801                                        }
802                                    });
803    
804                                }
805                            });
806                        }
807                    });
808                }
809            });
810    
811        }
812    
813        /**
814         * Parse fish and add them to the environment.
815         *
816         * @param l parser to read from
817         */
818        protected void parseFish(final Lexer l) {
819            while (l.nextToken().execute(new DefaultTokenVisitor() {
820                public Object defaultCase() {
821                    throw new ParserException("Invalid token");
822                }
823    
824                public Object endCase() {
825                    // end of stream
826                    // return false
827                    return Boolean.FALSE;
828                }
829    
830                public Object wordCase(String className) {
831                    // read class name
832                    try {
833                        // NOTE: Introduced class loader here
834                        // Class fishClass = Class.forName(className);
835                        Class fishClass = _securityAdapter.getClassLoader().loadClass(className);
836                        Constructor fishCtor = fishClass.getConstructor(new Class[]{Color.class});
837                        Random rng = RandNumGenerator.instance();
838                        AFish fish = (AFish) fishCtor.newInstance(new Object[]{new Color(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256))});
839    
840                        // read location
841                        Location loc = Location.parse(l);
842                        Direction dir = Direction.parse(l);
843                        LocalEnvironment localEnv = createLocalEnvironment(loc, dir);
844                        ILambda addLambda = addFish(localEnv, fish);
845                        addLambda.apply(null);
846    
847                        return Boolean.TRUE;
848                    }
849                    catch (Exception e) {
850                        e.printStackTrace();
851                        throw new ParserException(e.toString(),e);
852                    }
853                }
854            }) == Boolean.TRUE) {
855            }
856        }
857    
858        /**
859         * Get the environment settings class.
860         *
861         * @return environment settings class
862         */
863        public AEnvFactory makeEnvFactory() {
864            return new AEnvFactory() {
865                private JTextField _rotStepsField;
866                private JColorChooser _colorChooser;
867    
868                {
869                    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
870                    add(new JLabel("rotation steps: "));
871                    add(_rotStepsField = new JTextField("24"));
872                    add(new JLabel("water color: "));
873                    _colorChooser = new JColorChooser(OCEAN_BLUE);
874                    add(_colorChooser);
875                }
876    
877                public AGlobalEnv create() {
878                    return new NoGridEnv(_cmdFactory,
879                                         _securityAdapter,
880                                         Integer.parseInt(_rotStepsField.getText()),
881                                         _colorChooser.getColor());
882                }
883    
884                public String toString() {
885                    return NoGridEnv.class.getName();
886                }
887            };
888        }
889    
890        /**
891         * Print file header.
892         *
893         * @param pw PrintWriter to use
894         */
895        protected void printHeader(PrintWriter pw) {
896            pw.println(getClass().getName() + ' ' + _rotSteps + ' ' +
897                       _waterColor.getRed() + ' ' + _waterColor.getGreen() + ' ' + _waterColor.getBlue());
898        }
899    
900        /**
901         * Get size of the display.
902         *
903         * @return size of the display in model coordinate units.
904         */
905        public Dimension getDisplaySize() {
906            return new Dimension(PAN_SIZE, PAN_SIZE);
907        }
908    
909        /**
910         * The action to be executed if the display should return home.
911         *
912         * @param sa scroll adapter
913         */
914        public void returnHome(IScrollAdapter sa) {
915            sa.setCorner((int) PAN_CENTER.x, (int) PAN_CENTER.y);
916            sa.resetScrolling();
917        }
918    
919        /**
920         * Ask the model where to scroll, given where the user has scrolled. If the environment just acts like a normal
921         * panal, it should return pos without modification. If the environment recenters, it should return a position in
922         * the middle of the pan area. All coordinates are in model coordinate units.
923         *
924         * @param pos position where the user scrolled to
925         * @return position where the environment wants the view to be
926         * @see IDisplayAdapter#getPanDelta
927         */
928        public Point.Double getViewPosition(Point.Double pos) {
929            // the panel always gets recentered after moving it, so return the center position
930            return PAN_CENTER;
931        }
932    
933        /**
934         * Ask the model how much to pan, given where the user scrolled. If the environment just acts like a normal panal,
935         * it should return (0,0). If the environment recenters, it should return delta without modification. All
936         * coordinates are in model coordinate units.
937         *
938         * @param delta how far the user scrolled
939         * @return how far the panel should scroll
940         * @see IDisplayAdapter#getViewPosition
941         */
942        public Point.Double getPanDelta(Point.Double delta) {
943            // we want the panel to keep track of the position, so return the delta as pan value
944            return delta;
945        }
946    
947        /**
948         * Get a tool tip description for a specific place in the environment.
949         *
950         * @param p mouse coordinates
951         * @return lambda for the simulation controller to execute. Must return the tooltip string.
952         */
953        public ILambda getToolTipText(final Point.Double p) {
954            final LocalEnvironment l = createLocalEnvironment(new Location(p.x, p.y), new Direction());
955            return ((ILambda) l.execute(new ILocalEnvVisitor() {
956                public Object emptyCase(ALocalEnv host, Object param) {
957                    // this field is empty
958    
959                    // return an ILambda that returns the string for an empty field
960                    return new ILambda() {
961                        public Object apply(Object param) {
962                            return "(" + p.x + ',' + p.y + ") is empty";
963                        }
964                    };
965                }
966    
967                public Object nonEmptyCase(ALocalEnv host, Object param) {
968                    // this field is occupied
969    
970                    // return an ILambda that returns the string for an occupied
971                    return new ILambda() {
972                        String _fishName;
973                        ALocalEnv _localEnv;
974    
975                        public Object apply(Object param) {
976                            _fishName = "";
977                            _cmdFactory.makeNotifyCmd(new ILambda() {
978                                public Object apply(Object param) {
979                                    FishApplyParams fap = (FishApplyParams) param;
980                                    LocalEnvironment lenv = (LocalEnvironment) fap.localEnv();
981                                    if (lenv.location().inSquare(l.location(), lenv.direction(), 1)) {
982                                        _fishName = fap.fish().toString();
983                                        _localEnv = fap.localEnv();
984                                    }
985                                    return null;
986                                }
987                            }).apply(null);
988    
989                            return _fishName + " at " + _localEnv;
990                        }
991                    };
992                }
993            }, null));
994        }
995    
996    
997        /*****************************************************************************************************************
998         * Tests follow
999         *****************************************************************************************************************/
1000    
1001        /**
1002         * Test cases for NoGridEnv.
1003         *
1004         * @author Mathias Ricken
1005         */
1006        public static class Test_NoGridEnv extends TestCase {
1007            private ICmdFactory _cmdFactory;
1008            private ISecurityAdapter _sm;
1009            private NoGridEnv _env;
1010            private IFishFactory _fishFactory;
1011    
1012            private static final ILambda _notify = new ILambda() {
1013                public Object apply(Object param) {
1014                    return "notifyCmd";
1015                }
1016            };
1017            private static final ILambda _delete = new ILambda() {
1018                public Object apply(Object param) {
1019                    return "deleteCmd";
1020                }
1021            };
1022            private static final ILambda _add = new ILambda() {
1023                public Object apply(Object param) {
1024                    return "addCmd";
1025                }
1026            };
1027    
1028            public void setUp() throws Exception {
1029                super.setUp();
1030                _cmdFactory = new ICmdFactory() {
1031                    public ILambda makeNotifyCmd(ILambda lambda) {
1032                        return _notify;
1033                    }
1034    
1035                    public ILambda makeDeleteCmd(ALocalEnv env) {
1036                        return _delete;
1037                    }
1038    
1039                    public ILambda makeAddCmd(AFish fish) {
1040                        return _add;
1041                    }
1042                };
1043                _sm = new ISecurityAdapter() {
1044                    public void setProtected(boolean _protected) {
1045                    }
1046                    public ThreadGroup getFishThreadGroup() {
1047                        return null;
1048                    }
1049                    public ClassLoader getClassLoader() {
1050                        return null;
1051                    }
1052                    public void handleException(Throwable t) {
1053                    }
1054                };
1055    
1056                _env = new NoGridEnv(_cmdFactory, _sm, 24, OCEAN_BLUE);
1057    
1058    
1059                _fishFactory = new IFishFactory() {
1060                    /**
1061                     * Create a new fish.
1062                     *
1063                     * @return new fish
1064                     */
1065                    public AFish createFish() {
1066                        return new GenericFish(Color.RED);
1067                    }
1068                };
1069            }
1070    
1071            /**
1072             * Test addFishToInternalData.
1073             */
1074            public void testAddFish() {
1075                LRStruct lrs = _env._localEnvList;
1076    
1077                assertEquals(0, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1078    
1079                ALocalEnv lTop = _env.makeLocalEnv(new Point.Double(1.0, 1.0));
1080                GenericFish fTop = new GenericFish(Color.RED);
1081                fTop.setLocalEnvironment(lTop);
1082                _env.addFishToInternalData(lTop, fTop);
1083    
1084                assertEquals(1, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1085    
1086                ALocalEnv lBottom = _env.makeLocalEnv(new Point.Double(1.0, 2.0));
1087                GenericFish fBottom = new GenericFish(Color.RED);
1088                fBottom.setLocalEnvironment(lBottom);
1089                _env.addFishToInternalData(lBottom, fBottom);
1090    
1091                assertEquals(2, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1092            }
1093    
1094            /**
1095             * Test editFish.
1096             */
1097            public void testEditFish() {
1098                LRStruct lrs = _env._localEnvList;
1099    
1100                assertEquals(0, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1101    
1102                LocalEnvironment l = (LocalEnvironment) _env.makeLocalEnv(new Point.Double(1.0, 1.0));
1103                GenericFish f = new GenericFish(Color.RED);
1104                f.setLocalEnvironment(l);
1105                _env.addFishToInternalData(l, f);
1106                NoGridEnv.Direction d = l.direction();
1107    
1108                assertEquals(1, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1109                assertEquals(0.0, d.getAngle(), 0.01);
1110    
1111                ILambda lambda;
1112                for (int i = 1; 24 >= i; ++i) {
1113                    lambda = _env.editFish(l, _fishFactory, MouseEvent.BUTTON1);
1114                    assertEquals(1, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1115                    assertEquals(i * Math.PI / 12.0, d.getAngle(), 0.01);
1116                    assertEquals(NoOpLambda.instance(), lambda);
1117                }
1118    
1119                lambda = _env.editFish(l, _fishFactory, MouseEvent.BUTTON1);
1120                assertEquals(0, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1121                assertEquals(Math.PI / 12.0, d.getAngle(), 0.01);
1122                assertEquals(_delete, lambda);
1123            }
1124    
1125            /**
1126             * Test getViewPosition.
1127             */
1128            public void testGetViewPosition() {
1129                Point.Double panCenter = PAN_CENTER;
1130    
1131                assertTrue(_env.getViewPosition(new Point.Double(0, 0)).equals(panCenter));
1132                assertTrue(_env.getViewPosition(new Point.Double(1.0, 0)).equals(panCenter));
1133                assertTrue(_env.getViewPosition(new Point.Double(1.2, 0)).equals(panCenter));
1134                assertTrue(_env.getViewPosition(new Point.Double(0, 1.0)).equals(panCenter));
1135                assertTrue(_env.getViewPosition(new Point.Double(0, 1.3)).equals(panCenter));
1136                assertTrue(_env.getViewPosition(new Point.Double(-2.5, 0)).equals(panCenter));
1137                assertTrue(_env.getViewPosition(new Point.Double(-3.0, 0)).equals(panCenter));
1138                assertTrue(_env.getViewPosition(new Point.Double(0, -2.5)).equals(panCenter));
1139                assertTrue(_env.getViewPosition(new Point.Double(0, -3.0)).equals(panCenter));
1140                assertTrue(_env.getViewPosition(new Point.Double(2.0, 1.0)).equals(panCenter));
1141                assertTrue(_env.getViewPosition(new Point.Double(-4.0, -2.3)).equals(panCenter));
1142            }
1143    
1144            /**
1145             * Test getPanDelta.
1146             */
1147            public void testGetPanDelta() {
1148                assertTrue(_env.getPanDelta(new Point.Double(0, 0)).equals(new Point.Double(0, 0)));
1149                assertTrue(_env.getPanDelta(new Point.Double(1.0, 0)).equals(new Point.Double(1.0, 0)));
1150                assertTrue(_env.getPanDelta(new Point.Double(1.2, 0)).equals(new Point.Double(1.2, 0)));
1151                assertTrue(_env.getPanDelta(new Point.Double(0, 1.0)).equals(new Point.Double(0, 1.0)));
1152                assertTrue(_env.getPanDelta(new Point.Double(0, 1.3)).equals(new Point.Double(0, 1.3)));
1153                assertTrue(_env.getPanDelta(new Point.Double(-2.5, 0)).equals(new Point.Double(-2.5, 0)));
1154                assertTrue(_env.getPanDelta(new Point.Double(-3.0, 0)).equals(new Point.Double(-3.0, 0)));
1155                assertTrue(_env.getPanDelta(new Point.Double(0, -2.5)).equals(new Point.Double(0, -2.5)));
1156                assertTrue(_env.getPanDelta(new Point.Double(0, -3.0)).equals(new Point.Double(0, -3.0)));
1157                assertTrue(_env.getPanDelta(new Point.Double(2.0, 1.0)).equals(new Point.Double(2.0, 1.0)));
1158                assertTrue(_env.getPanDelta(new Point.Double(-4.0, -2.3)).equals(new Point.Double(-4.0, -2.3)));
1159            }
1160        }
1161    
1162        /**
1163         * Test cases for NoGridEnv.LocalEnv.
1164         *
1165         * @author Mathias Ricken
1166         */
1167        public static class Test_NoGridEnv_LocalEnv extends TestCase {
1168            private ICmdFactory _cmdFactory;
1169            private ISecurityAdapter _sm;
1170            private NoGridEnv _env;
1171    
1172            private static class SuccessException extends RuntimeException {
1173                public SuccessException() {
1174                    super();
1175                }
1176            }
1177    
1178            private static final ILambda _notify = new ILambda() {
1179                public Object apply(Object param) {
1180                    return "notifyCmd";
1181                }
1182            };
1183            private static final ILambda _delete = new ILambda() {
1184                public Object apply(Object param) {
1185                    return "deleteCmd";
1186                }
1187            };
1188            private static final ILambda _add = new ILambda() {
1189                public Object apply(Object param) {
1190                    return "addCmd";
1191                }
1192            };
1193    
1194            public void setUp() throws Exception {
1195                super.setUp();
1196                _cmdFactory = new ICmdFactory() {
1197                    public ILambda makeNotifyCmd(ILambda lambda) {
1198                        return _notify;
1199                    }
1200    
1201                    public ILambda makeDeleteCmd(ALocalEnv env) {
1202                        return _delete;
1203                    }
1204    
1205                    public ILambda makeAddCmd(AFish fish) {
1206                        return _add;
1207                    }
1208                };
1209                _sm = new ISecurityAdapter() {
1210                    public void setProtected(boolean _protected) {
1211                    }
1212                    public ThreadGroup getFishThreadGroup() {
1213                        return null;
1214                    }
1215                    public ClassLoader getClassLoader() {
1216                        return null;
1217                    }
1218                    public void handleException(Throwable t) {
1219                    }
1220                };
1221    
1222                _env = new NoGridEnv(_cmdFactory, _sm);
1223            }
1224    
1225            /**
1226             * Test local environment's execute.
1227             */
1228            public void testExecute() {
1229                ALocalEnv l = _env.makeLocalEnv(new Point.Double(1, 1));
1230    
1231                try {
1232                    l.execute(new AGlobalEnv.ILocalEnvVisitor() {
1233                        public Object emptyCase(ALocalEnv host, Object param) {
1234                            // ok
1235                            throw new SuccessException();
1236                        }
1237    
1238                        public Object nonEmptyCase(ALocalEnv host, Object param) {
1239                            throw new RuntimeException("Should be empty");
1240                        }
1241                    }, null);
1242                    fail("emptyCase should have been called --");
1243                }
1244                catch (SuccessException e) {
1245                }
1246    
1247    
1248                GenericFish f = new GenericFish(Color.RED);
1249                f.setLocalEnvironment(l);
1250                _env.addFish(l, f);
1251                try {
1252                    l.execute(new AGlobalEnv.ILocalEnvVisitor() {
1253                        public Object emptyCase(ALocalEnv host, Object param) {
1254                            throw new RuntimeException("Should be non-empty");
1255                        }
1256    
1257                        public Object nonEmptyCase(ALocalEnv host, Object param) {
1258                            // ok
1259                            throw new SuccessException();
1260                        }
1261                    }, null);
1262                    fail("nonEmptyCase should have been called --");
1263                }
1264                catch (SuccessException e) {
1265                }
1266    
1267                ALocalEnv l2 = _env.makeLocalEnv(new Point.Double(1, 1));
1268                try {
1269                    l2.execute(new AGlobalEnv.ILocalEnvVisitor() {
1270                        public Object emptyCase(ALocalEnv host, Object param) {
1271                            throw new RuntimeException("Should be non-empty");
1272                        }
1273    
1274                        public Object nonEmptyCase(ALocalEnv host, Object param) {
1275                            // ok
1276                            throw new SuccessException();
1277                        }
1278                    }, null);
1279                    fail("nonEmptyCase should have been called --");
1280                }
1281                catch (SuccessException e) {
1282                }
1283    
1284                ALocalEnv l3 = _env.makeLocalEnv(new Point.Double(1.4, 1.4));
1285                try {
1286                    l3.execute(new AGlobalEnv.ILocalEnvVisitor() {
1287                        public Object emptyCase(ALocalEnv host, Object param) {
1288                            throw new RuntimeException("Should be non-empty");
1289                        }
1290    
1291                        public Object nonEmptyCase(ALocalEnv host, Object param) {
1292                            // ok
1293                            throw new SuccessException();
1294                        }
1295                    }, null);
1296                    fail("nonEmptyCase should have been called --");
1297                }
1298                catch (SuccessException e) {
1299                }
1300    
1301    
1302                ALocalEnv l3b = _env.makeLocalEnv(new Point.Double(1.4, 1.6));
1303                try {
1304                    l3b.execute(new AGlobalEnv.ILocalEnvVisitor() {
1305                        public Object emptyCase(ALocalEnv host, Object param) {
1306                            // ok
1307                            throw new SuccessException();
1308                        }
1309    
1310                        public Object nonEmptyCase(ALocalEnv host, Object param) {
1311                            throw new RuntimeException("Should be empty");
1312                        }
1313                    }, null);
1314                    fail("emptyCase should have been called --");
1315                }
1316                catch (SuccessException e) {
1317                }
1318    
1319    
1320                ALocalEnv l4 = _env.makeLocalEnv(new Point.Double(1.0, 2.0));
1321                try {
1322                    l4.execute(new AGlobalEnv.ILocalEnvVisitor() {
1323                        public Object emptyCase(ALocalEnv host, Object param) {
1324                            // ok
1325                            throw new SuccessException();
1326                        }
1327    
1328                        public Object nonEmptyCase(ALocalEnv host, Object param) {
1329                            throw new RuntimeException("Should be empty");
1330                        }
1331                    }, null);
1332                    fail("emptyCase should have been called --");
1333                }
1334                catch (SuccessException e) {
1335                }
1336    
1337    
1338                GenericFish f4 = new GenericFish(Color.RED);
1339                f4.setLocalEnvironment(l4);
1340                _env.addFish(l4, f4);
1341                try {
1342                    l4.execute(new AGlobalEnv.ILocalEnvVisitor() {
1343                        public Object emptyCase(ALocalEnv host, Object param) {
1344                            throw new RuntimeException("Should be non-empty");
1345                        }
1346    
1347                        public Object nonEmptyCase(ALocalEnv host, Object param) {
1348                            // ok
1349                            throw new SuccessException();
1350                        }
1351                    }, null);
1352                    fail("nonEmptyCase should have been called --");
1353                }
1354                catch (SuccessException e) {
1355                }
1356            }
1357    
1358            /**
1359             * Test local environment's tryMoveFwd.
1360             */
1361            public void testTryMoveFwd() {
1362                ALocalEnv lTop = _env.makeLocalEnv(new Point.Double(1.0, 1.0));
1363                GenericFish fTop = new GenericFish(Color.RED);
1364                fTop.setLocalEnvironment(lTop);
1365                _env.addFish(lTop, fTop);
1366    
1367                ALocalEnv lBottom = _env.makeLocalEnv(new Point.Double(1.0, 2.0));
1368                GenericFish fBottom = new GenericFish(Color.RED);
1369                fBottom.setLocalEnvironment(lBottom);
1370                _env.addFish(lBottom, fBottom);
1371    
1372                // move lBottom into lTop --> blocked
1373                Integer i = (Integer) lBottom.tryMoveFwd(fBottom, new IBlockedCommand() {
1374                    public Object apply(Object param) {
1375                        // ok
1376                        return new Integer(456);
1377                    }
1378                }, new IOpenCommand() {
1379                    public Object apply(Object param) {
1380                        throw new RuntimeException("Should be blocked");
1381                    }
1382                });
1383                assertEquals("Error in delegation, blockedCmd not called, or incorrect return value --",
1384                             new Integer(456),
1385                             i);
1386    
1387                // move lTop --> open, don't move
1388                i = (Integer) lTop.tryMoveFwd(fTop, new IBlockedCommand() {
1389                    public Object apply(Object param) {
1390                        throw new RuntimeException("Should be open");
1391                    }
1392                }, new IOpenCommand() {
1393                    public Object apply(Object param) {
1394                        assertNotNull("Error, deactivatable move lambda needs to be passed to openCmd --", param);
1395                        assertEquals("Error, deactivatable move lambda needs to be passed to openCmd --",
1396                                     DeactivatableLambda.class,
1397                                     param.getClass());
1398                        // ok
1399                        return new Integer(123);
1400                    }
1401                });
1402                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(123), i);
1403    
1404                // move lBottom into lTop --> blocked
1405                i = (Integer) lBottom.tryMoveFwd(fBottom, new IBlockedCommand() {
1406                    public Object apply(Object param) {
1407                        // ok
1408                        return new Integer(789);
1409                    }
1410                }, new IOpenCommand() {
1411                    public Object apply(Object param) {
1412                        throw new RuntimeException("Should be blocked");
1413                    }
1414                });
1415                assertEquals("Error in delegation, blockedCmd not called, or incorrect return value --",
1416                             new Integer(789),
1417                             i);
1418    
1419                // move lTop --> open, move
1420                i = (Integer) lTop.tryMoveFwd(fTop, new IBlockedCommand() {
1421                    public Object apply(Object param) {
1422                        throw new RuntimeException("Should be open");
1423                    }
1424                }, new IOpenCommand() {
1425                    public Object apply(Object param) {
1426                        assertNotNull("Error, deactivatable move lambda needs to be passed to openCmd --", param);
1427                        assertEquals("Error, deactivatable move lambda needs to be passed to openCmd --",
1428                                     DeactivatableLambda.class,
1429                                     param.getClass());
1430                        // ok, make move
1431                        ((ILambda) param).apply(null);
1432                        return new Integer(111);
1433                    }
1434                });
1435                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(111), i);
1436    
1437                // move lBottom --> open
1438                i = (Integer) lBottom.tryMoveFwd(fBottom, new IBlockedCommand() {
1439                    public Object apply(Object param) {
1440                        throw new RuntimeException("Should be open");
1441                    }
1442                }, new IOpenCommand() {
1443                    public Object apply(Object param) {
1444                        assertNotNull("Error, deactivatable move lambda needs to be passed to openCmd --", param);
1445                        assertEquals("Error, deactivatable move lambda needs to be passed to openCmd --",
1446                                     DeactivatableLambda.class,
1447                                     param.getClass());
1448                        // ok
1449                        return new Integer(222);
1450                    }
1451                });
1452                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(222), i);
1453    
1454                // move lTop --> open, don't move
1455                i = (Integer) lTop.tryMoveFwd(fTop, new IBlockedCommand() {
1456                    public Object apply(Object param) {
1457                        throw new RuntimeException("Should be open");
1458                    }
1459                }, new IOpenCommand() {
1460                    public Object apply(Object param) {
1461                        assertNotNull("Error, deactivatable move lambda needs to be passed to openCmd --", param);
1462                        assertEquals("Error, deactivatable move lambda needs to be passed to openCmd --",
1463                                     DeactivatableLambda.class,
1464                                     param.getClass());
1465                        // ok
1466                        return new Integer(333);
1467                    }
1468                });
1469                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(333), i);
1470    
1471                // turn and move lTop --> open, don't move
1472                lTop.turnRight(fTop, Math.PI / 2.0);
1473                i = (Integer) lTop.tryMoveFwd(fTop, new IBlockedCommand() {
1474                    public Object apply(Object param) {
1475                        throw new RuntimeException("Should be open");
1476                    }
1477                }, new IOpenCommand() {
1478                    public Object apply(Object param) {
1479                        assertNotNull("Error, deactivatable move lambda needs to be passed to openCmd --", param);
1480                        assertEquals("Error, deactivatable move lambda needs to be passed to openCmd --",
1481                                     DeactivatableLambda.class,
1482                                     param.getClass());
1483                        // ok
1484                        return new Integer(444);
1485                    }
1486                });
1487                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(444), i);
1488            }
1489    
1490            /**
1491             * Test local environment's tryBreedFwd.
1492             */
1493            public void testTryBreedFwd() {
1494                LRStruct lrs = _env._localEnvList;
1495    
1496                assertEquals(0, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1497    
1498                ALocalEnv lTop = _env.makeLocalEnv(new Point.Double(1.0, 1.0));
1499                GenericFish fTop = new GenericFish(Color.RED);
1500                fTop.setLocalEnvironment(lTop);
1501                _env.addFish(lTop, fTop);
1502    
1503                assertEquals(1, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1504    
1505                ALocalEnv lBottom = _env.makeLocalEnv(new Point.Double(1.0, 2.0));
1506                GenericFish fBottom = new GenericFish(Color.RED);
1507                fBottom.setLocalEnvironment(lBottom);
1508                _env.addFish(lBottom, fBottom);
1509    
1510                assertEquals(2, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1511    
1512                // breed lBottom into lTop --> blocked
1513                Integer i = (Integer) lBottom.tryBreedFwd(fBottom, new IBlockedCommand() {
1514                    public Object apply(Object param) {
1515                        // ok
1516                        return new Integer(456);
1517                    }
1518                }, new IOpenCommand() {
1519                    public Object apply(Object param) {
1520                        throw new RuntimeException("Should be blocked");
1521                    }
1522                });
1523                assertEquals("Error in delegation, blockedCmd not called, or incorrect return value --",
1524                             new Integer(456),
1525                             i);
1526    
1527                // breed lTop --> open, don't breed
1528                i = (Integer) lTop.tryBreedFwd(fTop, new IBlockedCommand() {
1529                    public Object apply(Object param) {
1530                        throw new RuntimeException("Should be open");
1531                    }
1532                }, new IOpenCommand() {
1533                    public Object apply(Object param) {
1534                        assertNotNull("Error, deactivatable breed lambda needs to be passed to openCmd --", param);
1535                        assertEquals("Error, deactivatable breed lambda needs to be passed to openCmd --",
1536                                     DeactivatableLambda.class,
1537                                     param.getClass());
1538                        // ok
1539                        return new Integer(123);
1540                    }
1541                });
1542                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(123), i);
1543    
1544                assertEquals(2, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1545    
1546                // breed lBottom into lTop --> blocked
1547                i = (Integer) lBottom.tryBreedFwd(fBottom, new IBlockedCommand() {
1548                    public Object apply(Object param) {
1549                        // ok
1550                        return new Integer(456);
1551                    }
1552                }, new IOpenCommand() {
1553                    public Object apply(Object param) {
1554                        throw new RuntimeException("Should be blocked");
1555                    }
1556                });
1557                assertEquals("Error in delegation, blockedCmd not called, or incorrect return value --",
1558                             new Integer(456),
1559                             i);
1560    
1561                // breed lTop --> open, breed
1562                i = (Integer) lTop.tryBreedFwd(fTop, new IBlockedCommand() {
1563                    public Object apply(Object param) {
1564                        throw new RuntimeException("Should be open");
1565                    }
1566                }, new IOpenCommand() {
1567                    public Object apply(Object param) {
1568                        assertNotNull("Error, deactivatable breed lambda needs to be passed to openCmd --", param);
1569                        assertEquals("Error, deactivatable breed lambda needs to be passed to openCmd --",
1570                                     DeactivatableLambda.class,
1571                                     param.getClass());
1572                        // ok, breed
1573                        ((ILambda) param).apply(null);
1574                        return new Integer(123);
1575                    }
1576                });
1577                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(123), i);
1578    
1579                assertEquals(3, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1580    
1581                // breed lBottom into lTop --> blocked
1582                i = (Integer) lBottom.tryBreedFwd(fBottom, new IBlockedCommand() {
1583                    public Object apply(Object param) {
1584                        // ok
1585                        return new Integer(456);
1586                    }
1587                }, new IOpenCommand() {
1588                    public Object apply(Object param) {
1589                        throw new RuntimeException("Should be blocked");
1590                    }
1591                });
1592                assertEquals("Error in delegation, blockedCmd not called, or incorrect return value --",
1593                             new Integer(456),
1594                             i);
1595    
1596                // breed lTop --> blocked
1597                i = (Integer) lTop.tryBreedFwd(fTop, new IBlockedCommand() {
1598                    public Object apply(Object param) {
1599                        // ok
1600                        return new Integer(456);
1601                    }
1602                }, new IOpenCommand() {
1603                    public Object apply(Object param) {
1604                        throw new RuntimeException("Should be blocked");
1605                    }
1606                });
1607                assertEquals("Error in delegation, blockedCmd not called, or incorrect return value --",
1608                             new Integer(456),
1609                             i);
1610    
1611                // turn and breed lTop --> open, don't breed
1612                lTop.turnRight(fTop, Math.PI / 2.0);
1613                i = (Integer) lTop.tryBreedFwd(fTop, new IBlockedCommand() {
1614                    public Object apply(Object param) {
1615                        throw new RuntimeException("Should be open");
1616                    }
1617                }, new IOpenCommand() {
1618                    public Object apply(Object param) {
1619                        assertNotNull("Error, deactivatable breed lambda needs to be passed to openCmd --", param);
1620                        assertEquals("Error, deactivatable breed lambda needs to be passed to openCmd --",
1621                                     DeactivatableLambda.class,
1622                                     param.getClass());
1623                        // ok
1624                        return new Integer(789);
1625                    }
1626                });
1627                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(789), i);
1628    
1629                assertEquals(3, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1630    
1631                // turn and breed lTop --> open, breed
1632                i = (Integer) lTop.tryBreedFwd(fTop, new IBlockedCommand() {
1633                    public Object apply(Object param) {
1634                        throw new RuntimeException("Should be open");
1635                    }
1636                }, new IOpenCommand() {
1637                    public Object apply(Object param) {
1638                        assertNotNull("Error, deactivatable breed lambda needs to be passed to openCmd --", param);
1639                        assertEquals("Error, deactivatable breed lambda needs to be passed to openCmd --",
1640                                     DeactivatableLambda.class,
1641                                     param.getClass());
1642                        // ok, breed
1643                        ((ILambda) param).apply(null);
1644                        return new Integer(789);
1645                    }
1646                });
1647                assertEquals("Error in delegation, openCmd not called, or incorrect return value --", new Integer(789), i);
1648    
1649                assertEquals(4, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1650    
1651                // turn and breed lTop --> blocked
1652                i = (Integer) lTop.tryBreedFwd(fTop, new IBlockedCommand() {
1653                    public Object apply(Object param) {
1654                        // ok
1655                        return new Integer(789);
1656                    }
1657                }, new IOpenCommand() {
1658                    public Object apply(Object param) {
1659                        throw new RuntimeException("Should be blocked");
1660                    }
1661                });
1662                assertEquals("Error in delegation, blockedCmd not called, or incorrect return value --",
1663                             new Integer(789),
1664                             i);
1665    
1666                assertEquals(4, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1667            }
1668    
1669            /**
1670             * Test local environment's turnRight.
1671             */
1672            public void testTurnRight() {
1673                LocalEnvironment l = (LocalEnvironment) _env.makeLocalEnv(new Point.Double(1.0, 1.0));
1674                GenericFish f = new GenericFish(Color.RED);
1675                f.setLocalEnvironment(l);
1676                _env.addFish(l, f);
1677                NoGridEnv.Direction d = l.direction();
1678    
1679                assertEquals(0.0, d.getAngle(), 0.01);
1680                l.turnRight(f, Math.PI / 2);
1681                assertEquals(Math.PI / 2.0, d.getAngle(), 0.01);
1682                l.turnRight(f, Math.PI / 2);
1683                assertEquals(Math.PI, d.getAngle(), 0.01);
1684                l.turnRight(f, Math.PI / 2);
1685                assertEquals(3 * Math.PI / 2.0, d.getAngle(), 0.01);
1686                l.turnRight(f, Math.PI / 2);
1687                assertEquals(2 * Math.PI, d.getAngle(), 0.01);
1688            }
1689    
1690            /**
1691             * Test local environment's removeFish.
1692             */
1693            public void testRemoveFish() {
1694                LRStruct lrs = _env._localEnvList;
1695    
1696                assertEquals(0, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1697    
1698                ALocalEnv lTop = _env.makeLocalEnv(new Point.Double(1.0, 1.0));
1699                GenericFish fTop = new GenericFish(Color.RED);
1700                fTop.setLocalEnvironment(lTop);
1701                _env.addFish(lTop, fTop);
1702    
1703                assertEquals(1, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1704    
1705                ALocalEnv lBottom = _env.makeLocalEnv(new Point.Double(1.0, 2.0));
1706                GenericFish fBottom = new GenericFish(Color.RED);
1707                fBottom.setLocalEnvironment(lBottom);
1708                _env.addFish(lBottom, fBottom);
1709    
1710                assertEquals(2, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1711    
1712                lTop.removeFish(fTop);
1713    
1714                assertEquals(1, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1715    
1716                lBottom.removeFish(fBottom);
1717    
1718                assertEquals(0, ((Integer) lrs.execute(GetLength.Singleton, null)).intValue());
1719            }
1720    
1721            /**
1722             * Test to make sure only one move lambda can be executed.
1723             */
1724            public void testOnlyOneMove() {
1725                LocalEnvironment l = (LocalEnvironment) _env.makeLocalEnv(new Point.Double(5.0, 5.0));
1726                GenericFish f = new GenericFish(Color.RED);
1727                f.setLocalEnvironment(l);
1728                _env.addFish(l, f);
1729    
1730                final LinkedList<ILambda> lambdas = new LinkedList<ILambda>();
1731                l.tryMoveFwd(f, new IBlockedCommand() {
1732                    public Object apply(Object param) {
1733                        throw new RuntimeException("Should be open");
1734                    }
1735                }, new IOpenCommand() {
1736                    public Object apply(Object param) {
1737                        lambdas.add((ILambda)param);
1738                        return null;
1739                    }
1740                });
1741                f.turnRight();
1742                l.tryMoveFwd(f, new IBlockedCommand() {
1743                    public Object apply(Object param) {
1744                        throw new RuntimeException("Should be open");
1745                    }
1746                }, new IOpenCommand() {
1747                    public Object apply(Object param) {
1748                        lambdas.add((ILambda)param);
1749                        return null;
1750                    }
1751                });
1752    
1753                assertEquals(2, lambdas.size());
1754    
1755                lambdas.get(0).apply(null);
1756                NoGridEnv.Location loc = l.location();
1757                assertTrue(loc.same(new NoGridEnv.Location(5.0, 4.0)));
1758    
1759                lambdas.get(1).apply(null);
1760                loc = l.location();
1761                assertTrue(loc.same(new NoGridEnv.Location(5.0, 4.0)));
1762    
1763                lambdas.clear();
1764                l.tryMoveFwd(f, new IBlockedCommand() {
1765                    public Object apply(Object param) {
1766                        throw new RuntimeException("Should be open");
1767                    }
1768                }, new IOpenCommand() {
1769                    public Object apply(Object param) {
1770                        lambdas.add((ILambda)param);
1771                        return null;
1772                    }
1773                });
1774                f.turnRight();
1775                l.tryMoveFwd(f, new IBlockedCommand() {
1776                    public Object apply(Object param) {
1777                        throw new RuntimeException("Should be open");
1778                    }
1779                }, new IOpenCommand() {
1780                    public Object apply(Object param) {
1781                        lambdas.add((ILambda)param);
1782                        return null;
1783                    }
1784                });
1785    
1786                assertEquals(2, lambdas.size());
1787    
1788                lambdas.get(1).apply(null);
1789                loc = l.location();
1790                assertTrue(loc.same(new NoGridEnv.Location(6.0, 4.0)));
1791    
1792                lambdas.get(0).apply(null);
1793                loc = l.location();
1794                assertTrue(loc.same(new NoGridEnv.Location(6.0, 4.0)));
1795            }
1796        }
1797    
1798    }