1    // ============================================================================
2    //   Copyright 2006, 2007, 2008 Daniel W. Dyer
3    //
4    //   Licensed under the Apache License, Version 2.0 (the "License");
5    //   you may not use this file except in compliance with the License.
6    //   You may obtain a copy of the License at
7    //
8    //       http://www.apache.org/licenses/LICENSE-2.0
9    //
10   //   Unless required by applicable law or agreed to in writing, software
11   //   distributed under the License is distributed on an "AS IS" BASIS,
12   //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   //   See the License for the specific language governing permissions and
14   //   limitations under the License.
15   // ============================================================================
16   package org.uncommons.watchmaker.examples.travellingsalesman;
17   
18   import java.awt.BorderLayout;
19   import java.awt.event.ActionEvent;
20   import java.awt.event.ActionListener;
21   import java.util.Collection;
22   import java.util.List;
23   import javax.swing.JApplet;
24   import javax.swing.JOptionPane;
25   import javax.swing.JPanel;
26   import org.uncommons.swing.SwingBackgroundTask;
27   import org.uncommons.watchmaker.framework.FitnessEvaluator;
28   
29   /**
30    * Applet for comparing evolutionary and brute force approaches to the
31    * Travelling Salesman problem.
32    * @author Daniel Dyer
33    */
34   public final class TravellingSalesmanApplet extends JApplet
35   {
36       private final ItineraryPanel itineraryPanel;
37       private final StrategyPanel strategyPanel;
38       private final ExecutionPanel executionPanel;
39   
40       private final FitnessEvaluator<List<String>> evaluator;
41   
42   
43       /**
44        * Creates the applet and lays out its GUI.
45        */
46       public TravellingSalesmanApplet()
47       {
48           DistanceLookup distances = new EuropeanDistanceLookup();
49           evaluator = new RouteEvaluator(distances);
50           itineraryPanel = new ItineraryPanel(distances.getKnownCities());
51           strategyPanel = new StrategyPanel(distances);
52           executionPanel = new ExecutionPanel();
53           add(itineraryPanel, BorderLayout.WEST);
54           JPanel innerPanel = new JPanel(new BorderLayout());
55           innerPanel.add(strategyPanel, BorderLayout.NORTH);
56           innerPanel.add(executionPanel, BorderLayout.CENTER);
57           add(innerPanel, BorderLayout.CENTER);
58           
59           executionPanel.addActionListener(new ActionListener()
60           {
61               public void actionPerformed(ActionEvent actionEvent)
62               {
63                   Collection<String> cities = itineraryPanel.getSelectedCities();
64                   if (cities.size() < 4)
65                   {
66                       JOptionPane.showMessageDialog(TravellingSalesmanApplet.this,
67                                                     "Itinerary must include at least 4 cities.",
68                                                     "Error",
69                                                     JOptionPane.ERROR_MESSAGE);
70                   }
71                   else
72                   {
73                       try
74                       {
75                           setEnabled(false);
76                           createTask(cities).execute();
77                       }
78                       catch (IllegalArgumentException ex)
79                       {
80                           JOptionPane.showMessageDialog(TravellingSalesmanApplet.this,
81                                                         ex.getMessage(),
82                                                         "Error",
83                                                         JOptionPane.ERROR_MESSAGE);
84                           setEnabled(true);
85                       }
86                   }
87               }
88           });
89           validate();
90       }
91   
92   
93       /**
94        * Helper method to create a background task for running the travelling
95        * salesman algorithm.
96        * @param cities The set of cities to generate a route for.
97        * @return A Swing task that will execute on a background thread and update
98        * the GUI when it is done.
99        */
100      private SwingBackgroundTask<List<String>> createTask(final Collection<String> cities)
101      {
102          final TravellingSalesmanStrategy strategy = strategyPanel.getStrategy();
103          return new SwingBackgroundTask<List<String>>()
104          {
105              private long elapsedTime = 0;
106  
107              protected List<String> performTask()
108              {
109                  long startTime = System.currentTimeMillis();
110                  List<String> result = strategy.calculateShortestRoute(cities, executionPanel);
111                  elapsedTime = System.currentTimeMillis() - startTime;
112                  return result;
113              }
114  
115              protected void postProcessing(List<String> result)
116              {
117                  executionPanel.appendOutput(createResultString(strategy.getDescription(),
118                                                                 result,
119                                                                 evaluator.getFitness(result, null),
120                                                                 elapsedTime));
121                  setEnabled(true);
122              }
123          };
124      }
125  
126  
127      /**
128       * Helper method for formatting a result as a string for display.
129       */
130      private String createResultString(String strategyDescription,
131                                        List<String> shortestRoute,
132                                        double distance,
133                                        long elapsedTime)
134      {
135          StringBuilder buffer = new StringBuilder();
136          buffer.append('[');
137          buffer.append(strategyDescription);
138          buffer.append("]\n");
139          buffer.append("ROUTE: ");
140          for (String s : shortestRoute)
141          {
142              buffer.append(s);
143              buffer.append(" -> ");
144          }
145          buffer.append(shortestRoute.get(0));
146          buffer.append('\n');
147          buffer.append("TOTAL DISTANCE: ");
148          buffer.append(String.valueOf(distance));
149          buffer.append("km\n");
150          buffer.append("(Search Time: ");
151          double seconds = (double) elapsedTime / 1000;
152          buffer.append(String.valueOf(seconds));
153          buffer.append(" seconds)\n\n");
154          return buffer.toString();
155      }
156  
157  
158      /**
159       * Toggles whether the controls are enabled for input or not.
160       * @param b Enables the controls if this flag is true, disables them otherwise.
161       */
162      @Override
163      public void setEnabled(boolean b)
164      {
165          itineraryPanel.setEnabled(b);
166          strategyPanel.setEnabled(b);
167          executionPanel.setEnabled(b);
168          super.setEnabled(b);
169      }
170  }
171