001    package sysModel;
002    
003    import controller.IEditAdapter;
004    import controller.IScrollAdapter;
005    import controller.ISimulationExceptionAdapter;
006    import controller.IDisplayAdapter;
007    import model.ILambda;
008    import sysModel.env.AEnvFactory;
009    import sysModel.env.AGlobalEnv;
010    import sysModel.env.FishApplyParams;
011    import sysModel.env.NullEnv;
012    import sysModel.fish.DynamicFishFactory;
013    import sysModel.fish.AFish;
014    import sysModel.parser.Parser;
015    import sysModel.parser.ParserException;
016    
017    import javax.swing.*;
018    import java.awt.*;
019    import java.awt.event.ActionEvent;
020    import java.awt.event.ActionListener;
021    import java.io.FileWriter;
022    import java.io.PrintWriter;
023    import java.io.IOException;
024    
025    /**
026     * Simulation control coordinating interaction between view, simulation engine and environment.
027     *
028     * @author Mathias Ricken
029     */
030    public class SimDriver {
031        /**
032         * Environment.
033         */
034        private AGlobalEnv _env;
035    
036        /**
037         * Simulation engine.
038         */
039        private SimEngine _simEngine;
040    
041        /**
042         * Command to set the seed when a new environment is loaded or created.
043         */
044        private ILambda _seedCommand;
045    
046        /**
047         * Command executed before the simulation starts running.
048         */
049        private ILambda _startCommand;
050    
051        /**
052         * Command executed after the simulation finishes an iteration.
053         */
054        private ILambda _iterationCommand;
055    
056        /**
057         * Simulation speed.
058         */
059        private int _simulationSpeed;
060    
061        /**
062         * Processing timer.
063         */
064        private Timer _timer;
065    
066        /**
067         * Edit adapter.
068         */
069        private IEditAdapter _editAdapter = null;
070    
071        /**
072         * Simulation exception adapter.
073         */
074        private ISimulationExceptionAdapter _simulationExceptionAdapter = null;
075    
076        /**
077         * Security manager.
078         */
079        private MBSSecurityManager _securityManager = null;
080    
081        /**
082         * Command factory for use in the environment.
083         */
084        private ICmdFactory _cmdFactory = new ICmdFactory() {
085            public ILambda makeNotifyCmd(final ILambda lambda) {
086                return new ILambda() {
087                    public Object apply(Object param) {
088                        _simEngine.notify(lambda);
089                        return null;
090                    }
091                };
092            }
093    
094            public ILambda makeDeleteCmd(final AGlobalEnv.ALocalEnv env) {
095                return makeNotifyCmd(new ILambda() {
096                    public Object apply(Object param) {
097                        FishApplyParams applyParams = (FishApplyParams) param;
098                        // if the fish is at this location
099                        if (applyParams.localEnv() == env) {
100                            // ask the simulation engine to remove it
101                            _simEngine.delete(applyParams.fish());
102                        }
103                        return null;
104                    }
105                });
106            }
107    
108            public ILambda makeAddCmd(final AFish fish) {
109                return new ILambda() {
110                    public Object apply(Object param) {
111                        _simEngine.add(fish);
112                        return null;
113                    }
114                };
115            }
116        };
117    
118        /**
119         * Construct a new model.
120         */
121        public SimDriver() {
122            _env = NullEnv.instance();
123            _simEngine = new SimEngine();
124            _seedCommand = NoOpLambda.instance();
125            _startCommand = NoOpLambda.instance();
126            _iterationCommand = NoOpLambda.instance();
127            // Note: DrJava incompatibility.
128            _securityManager = new MBSSecurityManager("true".equals(System.getProperty("edu.rice.cs.cunit.drjava")));
129            _securityManager.setProtected(false);
130            System.setSecurityManager(_securityManager);
131        }
132    
133        /**
134         * Get the simulation engine.
135         *
136         * @return simulation engine
137         */
138        public ICmdFactory getCmdFactory() {
139            return _cmdFactory;
140        }
141    
142        /**
143         * Set adapters.
144         *
145         * @param ea makeEditCmd adapter to use
146         * @param sea simulation exception adapter to use
147         */
148        public void setAdapters(IEditAdapter ea, ISimulationExceptionAdapter sea) {
149            _editAdapter = ea;
150            _simulationExceptionAdapter = sea;
151        }
152    
153        /**
154         * Load environment from file.
155         *
156         * @param filename filename
157         * @return true if successful
158         */
159        public boolean loadEnvironment(String filename) {
160            try {
161                _simEngine.clear();
162                _env = Parser.parse(filename, _cmdFactory, getSecurityAdapter());
163                _seedCommand.apply(null);
164                return true;
165            }
166            catch (ParserException e) {
167                System.out.println(e);
168                return false;
169            }
170        }
171    
172        /**
173         * Save environment to file.
174         *
175         * @param filename filename
176         * @return true if successful
177         */
178        public boolean saveEnvironment(String filename) {
179            try {
180                // open a new PrintWriter
181                PrintWriter pw = new PrintWriter(new FileWriter(filename));
182    
183                // ask the environment what to do
184                ILambda lambda = _env.save(pw);
185    
186                // and do it
187                _simEngine.notify(lambda);
188    
189                // close the PrintWriter
190                pw.close();
191                return true;
192            }
193            catch (IOException e) {
194                return false;
195            }
196        }
197    
198        /**
199         * Create environment from factory.
200         *
201         * @param factory factory
202         * @return true if successful
203         */
204        public boolean createEnvironment(AEnvFactory factory) {
205            _env = factory.create();
206            _simEngine.clear();
207            _seedCommand.apply(null);
208            return true;
209        }
210    
211        /**
212         * Set the seed command. This command gets executed before a new environment is created or loaded.
213         *
214         * @param seedCmd command to set
215         */
216        public void setSeedLambda(ILambda seedCmd) {
217            _seedCommand = seedCmd;
218        }
219    
220        /**
221         * Make a simulation step.
222         */
223        public void step() {
224            // ask the environment what to do
225            ILambda lambda = _env.makeStepCmd();
226    
227            // and do it
228            lambda.apply(this);
229        }
230    
231        /**
232         * Start simulation.
233         */
234        public void start() {
235            _startCommand.apply(null);
236            _timer = new Timer(_simulationSpeed, new ActionListener() {
237                public void actionPerformed(ActionEvent evt) {
238                    step();
239                    _iterationCommand.apply(null);
240                }
241            });
242            _timer.setCoalesce(false);
243            _timer.start();
244        }
245    
246        /**
247         * Stop simulation.
248         */
249        public void stop() {
250            if (null != _timer) {
251                _timer.stop();
252            }
253        }
254    
255        /**
256         * Change the simulation speed.
257         *
258         * @param speed simulation speed
259         */
260        public void setSpeed(int speed) {
261            _simulationSpeed = speed;
262            if (null != _timer) {
263                _timer.setDelay(speed);
264            }
265        }
266    
267        /**
268         * Set simulation start command. This command gets called before the simulation starts.
269         *
270         * @param startCmd start command
271         */
272        public void setStartLambda(ILambda startCmd) {
273            _startCommand = startCmd;
274        }
275    
276        /**
277         * Set simulation iteration command. This command gets called after the simulation finishes a step.
278         *
279         * @param itCmd start command
280         */
281        public void setIterationLambda(ILambda itCmd) {
282            _iterationCommand = itCmd;
283        }
284    
285        /**
286         * Draw model in this region. The graphics object has been set up so that (0,0) represents the top left and
287         * (100,100) the bottom right corner.
288         *
289         * @param g    graphics object
290         * @param comp component to drawFish on
291         * @param p1   top left corner of the region
292         * @param p2   bottom right corner of the region
293         */
294        public void draw(Graphics2D g, Component comp, Point.Double p1, Point.Double p2) {
295            // ask the environment what to do
296            ILambda lambda = _env.makeDrawCmd(g, comp, p1, p2);
297    
298            // and do it
299            lambda.apply(this);
300        }
301    
302        /**
303         * Edit a field.
304         *
305         * @param p      coordinates in model coordinate units
306         * @param button mouse button pressed
307         */
308        public void edit(Point.Double p, int button) {
309            // create the fishFactory to use
310            DynamicFishFactory fishFactory = new DynamicFishFactory(_editAdapter.getCurrentFish(),
311                                                                    _editAdapter.getCurrentColor(),
312                                                                    new ISecurityAdapter() {
313                                                                        public void setProtected(boolean b) {
314                                                                            _securityManager.setProtected(b);
315                                                                        }
316                                                                        public ThreadGroup getFishThreadGroup() {
317                                                                            return _securityManager.getFishThreadGroup();
318                                                                        }
319                                                                        public ClassLoader getClassLoader() {
320                                                                            return _securityManager.getClassLoader();
321                                                                        }
322                                                                        public void handleException(Throwable t) {
323                                                                            _simulationExceptionAdapter.handleException(t);
324                                                                        }
325                                                                    });
326    
327            try {
328                // let the environment decide what to do
329                ILambda lambda = _env.makeEditCmd(p, fishFactory, button);
330    
331                // and do it
332                lambda.apply(this);
333            }
334            catch(Throwable t) {
335                Throwable d = t;
336                while ((null != d) && (null != d.getCause())) {
337                    d = d.getCause();
338                }
339                _simulationExceptionAdapter.handleException(d);
340            }
341        }
342    
343        /**
344         * The action to be executed if the display should return home.
345         *
346         * @param sa scroll adapter
347         */
348        public void returnHome(IScrollAdapter sa) {
349            _env.returnHome(sa);
350        }
351    
352        /**
353         * Get size of the display.
354         *
355         * @return size of the display in model coordinate units.
356         */
357        public Dimension getDisplaySize() {
358            return _env.getDisplaySize();
359        }
360    
361        /**
362         * Ask the model where to scroll, given where the user has scrolled. If the environment just acts like a normal
363         * panal, it should return pos without modification. If the environment recenters, it should return a position in
364         * the middle of the pan area. All coordinates are in model coordinate units.
365         *
366         * @param pos position where the user scrolled to
367         * @return position where the environment wants the view to be
368         * @see IDisplayAdapter#getPanDelta
369         */
370        public Point.Double getViewPosition(Point.Double pos) {
371            return _env.getViewPosition(pos);
372        }
373    
374        /**
375         * Ask the model how much to pan, given where the user scrolled. If the environment just acts like a normal panal,
376         * it should return (0,0). If the environment recenters, it should return delta without modification. All
377         * coordinates are in model coordinate units.
378         *
379         * @param delta how far the user scrolled
380         * @return how far the panel should scroll
381         * @see IDisplayAdapter#getViewPosition
382         */
383        public Point.Double getPanDelta(Point.Double delta) {
384            return _env.getPanDelta(delta);
385        }
386    
387        /**
388         * Get a tool tip description for a specific place in the environment.
389         *
390         * @param p mouse coordinates
391         * @return tool tip text
392         */
393        public String getToolTipText(Point.Double p) {
394            return ((String) _env.getToolTipText(p).apply(this));
395        }
396    
397        /**
398         * Return the security adapter.
399         *
400         * @return security adapter
401         */
402        public ISecurityAdapter getSecurityAdapter() {
403            return new ISecurityAdapter() {
404                public void setProtected(boolean b) {
405                    // Note: DrJava incompatibility.
406                    _securityManager.setProtected(b);
407                }
408                public ThreadGroup getFishThreadGroup() {
409                    return _securityManager.getFishThreadGroup();
410                }
411                public ClassLoader getClassLoader() {
412                    return _securityManager.getClassLoader();
413                }
414                public void handleException(Throwable t) {
415                    _simulationExceptionAdapter.handleException(t);
416                }
417            };
418        }
419    }