001    package org.LiveGraph;
002    
003    import javax.swing.JOptionPane;
004    
005    import org.LiveGraph.bootstrap.CommandLineProcessor;
006    import org.LiveGraph.bootstrap.UpgradeManager;
007    import org.LiveGraph.dataCache.DataCache;
008    import org.LiveGraph.dataCache.DataStreamToCacheReader;
009    import org.LiveGraph.dataCache.UpdateInvoker;
010    import org.LiveGraph.events.EventManager;
011    import org.LiveGraph.gui.GUIManager;
012    import org.LiveGraph.plot.GraphExporter;
013    import org.LiveGraph.plot.Plotter;
014    import org.LiveGraph.settings.DataFileSettings;
015    import org.LiveGraph.settings.DataSeriesSettings;
016    import org.LiveGraph.settings.GraphSettings;
017    
018    import com.softnetConsult.utils.exceptions.ThrowableTools;
019    
020    
021    /**
022     * This is the main executable class of the LiveGraph plotter application.
023     * An instance of this class represents the application itself. The tasks of this
024     * class is to interpret the command line parameters, to set-up and to start-up
025     * the GUI and the back-end of the application, and to provide some
026     * functions which are used by different modules of the application to communicate
027     * with each other and to access global data, such as settings.
028     * 
029     * <p style="font-size:smaller;">This product includes software developed by the
030     *    <strong>LiveGraph</strong> project and its contributors.<br />
031     *    (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
032     *    Copyright (c) 2007-2008 G. Paperin.<br />
033     *    All rights reserved.
034     * </p>
035     * <p style="font-size:smaller;">File: LiveGraph.java</p>
036     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
037     *    without modification, are permitted provided that the following terms and conditions are met:
038     * </p>
039     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
040     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
041     *    this list of conditions and the following disclaimer.<br />
042     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
043     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
044     *    and the following disclaimer in the documentation and/or other materials provided with
045     *    the distribution.<br />
046     *    3. All advertising materials mentioning features or use of this software or any derived
047     *    software must display the following acknowledgement:<br />
048     *    <em>This product includes software developed by the LiveGraph project and its
049     *    contributors.<br />(http://www.live-graph.org)</em><br />
050     *    4. All advertising materials distributed in form of HTML pages or any other technology
051     *    permitting active hyper-links that mention features or use of this software or any
052     *    derived software must display the acknowledgment specified in condition 3 of this
053     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
054     *    homepage (http://www.live-graph.org).
055     * </p>
056     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
057     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
058     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
059     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
060     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
061     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
062     * </p> 
063     *  
064     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
065     * @version {@value org.LiveGraph.LiveGraph#version}
066     */
067    public class LiveGraph {
068    
069    /**
070     * LiveGraph software version.
071     */
072    public static final String version = "2.0.beta01";
073    
074    // Static stuff {
075    
076    /**
077     * Singleton application instance.
078     */
079    private static LiveGraph app = null;
080    
081    /**
082     * Singleton application access method.
083     * 
084     * @return The singleton application object.
085     */
086    public static LiveGraph application() {
087            if (null == LiveGraph.app)
088                    LiveGraph.app = new LiveGraph();
089            return LiveGraph.app;
090    }
091    
092    /**
093     * Program entry point.
094     * Creates an application instance and calls the {@link #execStandalone(String[])} method.
095     * 
096     * @param args Command line parameters.
097     */
098    public static void main(String [] args) {       
099            LiveGraph.application().execStandalone(args);
100    }
101    
102    // } end of static stuff.
103    
104    
105    private EventManager eventManager = null;
106    private GUIManager guiManager = null;
107    private DataCache dataCache = null;
108    private UpgradeManager upgradeManager = null;
109    private boolean initialised = false;
110    private boolean standAlone = false;
111    
112    private Thread.UncaughtExceptionHandler commonDefaultUncaughtExceptionHandler = null;
113    
114    /**
115     * Application's data update invoker.
116     */
117    private UpdateInvoker updateInvoker = null;
118    
119    
120    /**
121     * Holds the data file settings for the application.
122     */
123    private DataFileSettings dataFileSettings = null;
124    
125    /**
126     * Holds the graph settings for the application.
127     */
128    private GraphSettings graphSettings = null;
129    
130    /**
131     * Holds the data series settings for the application.
132     */
133    private DataSeriesSettings seriesSettings = null;
134    
135    /**
136     * Holds the graph exporter.
137     */
138    private GraphExporter graphExporter = null;
139    
140    
141    public void execStandalone() {
142            execStandalone(new CommandLineProcessor());
143    }
144    
145    /**
146     * Main program method.
147     * It parses the command line parameters, sets up the GUI and the back-end components
148     * of the application and configures the their communication. It then loads the default
149     * settings and passes the execution control to the main Swing GUI loop. 
150     * 
151     * @param args Command line arguments.
152     */
153    public void execStandalone(String[] args) {
154            execStandalone(new CommandLineProcessor(args));
155    }
156    
157    public void execStandalone(CommandLineProcessor cmdLn) {
158            
159            // Ensure command line info is present:
160            if (null == cmdLn)
161                    cmdLn = new CommandLineProcessor();
162            
163            // Start engine:
164            execEngine();
165            standAlone = true;
166            
167            // Set up application upgrade manager:
168            upgradeManager = new UpgradeManager();
169            
170            // Create application windows:
171            guiManager.createPlotWindow();
172            guiManager.setDisplayPlotWindows(true);
173            
174            guiManager.createSeriesSettingsWindow();
175            guiManager.setDisplaySeriesSettingsWindows(true);
176            
177            guiManager.createDataFileSettingsWindow();
178            guiManager.setDisplayDataFileSettingsWindows(true);
179            
180            guiManager.createGraphSettingsWindow();
181            guiManager.setDisplayGraphSettingsWindows(true);
182            
183            guiManager.createMessageWindow();
184            guiManager.setDisplayMessageWindows(false);
185            
186            // Display any possible error messages about the command line arguments:
187            if (cmdLn.hasErrors()) {
188                    String errMsg = cmdLn.getErrorMessages();
189                    System.out.println();
190                    System.out.println(errMsg);
191                    guiManager.logErrorLn(errMsg);
192            }
193            
194            // Load default or command-line specified graph settings:
195            if (null != cmdLn.getFile_GraphSettings()) {
196                    String fn = cmdLn.getFile_GraphSettings().getAbsolutePath();
197                    guiManager.logInfoLn("Attempting to load initial graph settings from \"" + fn + "\".");
198                    if (!graphSettings.load(fn))
199                            guiManager.logErrorLn("Error while loading initial graph settings from \"" + fn + "\".");
200            }
201            
202            // Load default or command-line specified data series settings:
203            if (null != cmdLn.getFile_DataSeriesSettings()) {
204                    String fn = cmdLn.getFile_DataSeriesSettings().getAbsolutePath();
205                    guiManager.logInfoLn("Attempting to load initial data series settings from \"" + fn + "\".");
206                    if (!seriesSettings.load(fn))
207                            guiManager.logErrorLn("Error while loading initial data series settings from \"" + fn + "\".");
208            }
209            
210            // Load default or command-line specified data file settings:
211            if (null != cmdLn.getFile_DataFileSettings()) {
212                    String fn = cmdLn.getFile_DataFileSettings().getAbsolutePath();
213                    boolean ignoreDataFile = (null != cmdLn.getFile_Data());
214                    guiManager.logInfoLn("Attempting to load initial data file settings from \"" + fn + "\".");
215                    if (!dataFileSettings.load(fn, ignoreDataFile))
216                            guiManager.logErrorLn("Error while loading initial data file settings from \"" + fn + "\".");
217            }
218            
219            // Load command-line specified data file:
220            if (null != cmdLn.getFile_Data()) {
221                    String fn = cmdLn.getFile_Data().getAbsolutePath();
222                    guiManager.logInfoLn("Attempting to read initial data from \"" + fn + "\".");
223                    dataFileSettings.setDataFile(fn);
224            }
225            
226            // Initiate automatik upgrade check if needed:
227            upgradeManager.autoUpdate();
228    }
229    
230    public synchronized void execEngine() {
231            
232            if (initialised)
233                    throw new IllegalStateException("Cannot start LiveGraph engine as it is already running");
234            
235            // Setup exception handling:
236            installUncaughtExceptionHandler();
237            
238            // Create the global event manager:
239            eventManager = new EventManager();
240            eventManager.addShutDownHook(new TidyUpAfterEventManagerShutDown());
241            
242            // Create the data cache:
243            dataCache = new DataCache();
244            
245            // Create the data reader:
246            DataStreamToCacheReader dataReader = new DataStreamToCacheReader(dataCache);
247            eventManager.registerListener(dataReader);
248            
249            // Create the global GUI manager:
250            guiManager = new GUIManager();
251            guiManager.setDataCache(dataCache);
252            
253            // Create settings holder objects:
254            dataFileSettings = new DataFileSettings();
255            graphSettings = new GraphSettings();
256            seriesSettings = new DataSeriesSettings();
257            
258            // Create the data update invoker,
259            // set-up its communication with the cache and other objects,
260            // and create the data update invocation thread:        
261            updateInvoker = new UpdateInvoker(dataCache);
262            eventManager.registerListener(updateInvoker);
263            Thread fileUpdateInvokerThread = new Thread(updateInvoker, "LiveGraph Update Invoker Thread");
264            
265            // Start the event dispatching thread:
266            eventManager.startDispatchingEvents();
267    
268            // Start the data update invocation thread:
269            fileUpdateInvokerThread.start();
270            
271            // Check for correct Java version and display an error message if wrong version is detected:
272            if (!runsCorrectJavaVersion()) {
273                    JOptionPane.showMessageDialog(null, "The Java runtime environment you are using may not "
274                                                                                      + "support all program features.\n\n"
275                                                                                      + "LiveGraph is targeted for Java version 1.6 or later, "
276                                                                                      + "however, it may run on earlier Java versions with a "
277                                                                                      + "reduced feature set.\nNote that various error messages "
278                                                                                      + "may be displayed when accessing the unsupported features.\n\n"
279                                                                                      + "Your current Java version is " + getJavaSpecificationVersion(),
280                                                                              "Incompatible Java version", JOptionPane.WARNING_MESSAGE);
281            }
282            
283            standAlone = false;
284            initialised = true;
285    }
286    
287    
288    /**
289     * Determines the current Java specification version. 
290     * @return The current Java specification version or {@code "unknown"} if it could not be obtained.
291     */
292    public String getJavaSpecificationVersion() {
293            try {
294                    String ver = System.getProperty("java.specification.version"); 
295                    return (null == ver ? "unknown" : ver);
296            } catch (Throwable e) {
297                    return "unknown";
298            }
299    }
300    
301    /**
302     * Determines whether the currect Java version is appropriate. This is done based on the system
303     * property {@code java.specification.version}. Java version {@code 1.6} or higher is considered ok.
304     * @return Whether the currect Java version is appropriate.
305     */
306    public boolean runsCorrectJavaVersion() {
307            
308            String specVer = getJavaSpecificationVersion(); 
309                    
310            if (specVer.equalsIgnoreCase("unknown"))
311                    return false;
312            
313            int p = specVer.indexOf(".");
314            if (0 > p)
315                    return false;
316            
317            int mainVer = Integer.parseInt(specVer.substring(0, p));
318            if (1 > mainVer)
319                    return false;
320            if (1 < mainVer)
321                    return true;
322            
323            if (specVer.length() - 1 <= p)
324                    return false;
325            
326            int subVer =  Integer.parseInt(specVer.substring(p + 1, p + 2));
327            if (6 > subVer)
328                    return false;
329            
330            return true;
331    }
332    
333    
334    /**
335     * This method is called by the main window when it is closed. This method
336     * initiates the disposing of all windows and the data update invocation
337     * thread in order to correctly close the application and save all settings
338     * to default files. API users should call ths method in order to shut down
339     * LiveGraph.
340     */
341    public void disposeGUIAndExit() {
342            
343            if (!initialised)
344                    throw new IllegalStateException("Cannot shut down LiveGraph since it is not running");
345            
346            if (standalone()) {
347                    try {
348                            dataFileSettings.save("session" + DataFileSettings.preferredFileExtension);
349                            graphSettings.save("session" + GraphSettings.preferredFileExtension);
350                            seriesSettings.save("session" + DataSeriesSettings.preferredFileExtension);
351                    } catch(SecurityException e) {}
352            }
353            
354            if (null != graphExporter) {
355                    graphExporter.disposeInternalGUI();
356                    graphExporter = null;
357            }
358            
359            updateInvoker.setMustQuit(true);
360            updateInvoker = null;
361            
362            guiManager.disposeAllGUI();     
363            eventManager.shutDownWhenFinished();
364    }
365    
366    private class TidyUpAfterEventManagerShutDown implements EventManager.ShutDownHook {
367            public void hasShutDown(EventManager evMan) {
368                    if (null == evMan || evMan != evMan)
369                            throw new IllegalArgumentException(""+evMan);
370                
371                    shutDown();
372        }
373    }
374    
375    private synchronized void shutDown() {
376            
377            if (null != updateInvoker)
378                    updateInvoker.setMustQuit(true);
379            updateInvoker = null;
380            
381            if (null != commonDefaultUncaughtExceptionHandler) {
382                    try {
383                            Thread.setDefaultUncaughtExceptionHandler(commonDefaultUncaughtExceptionHandler);
384                    } catch (SecurityException e) { }
385            }
386            commonDefaultUncaughtExceptionHandler = null;
387                    
388            eventManager = null;
389            guiManager = null;
390            dataCache = null;
391            upgradeManager = null;
392            dataFileSettings = null;
393            graphSettings = null;
394            seriesSettings = null;
395            graphExporter = null;
396            standAlone = false;
397            initialised = false;
398            LiveGraph.app = null;
399    }
400    
401    
402    /**
403     * Gets the applications' global event manager.
404     * 
405     * @return Global event manager.
406     */
407    public EventManager eventManager() {
408            return this.eventManager;
409    }
410    
411    /**
412     * Gets the applications' global gui manager.
413     * 
414     * @return Global event manager.
415     */
416    public GUIManager guiManager() {
417            return this.guiManager;
418    }
419    
420    public UpdateInvoker updateInvoker() {
421            return this.updateInvoker;
422    }
423    
424    public UpgradeManager upgradeManager() {
425            return this.upgradeManager;
426    }
427    
428    public boolean standalone() {
429            return this.standAlone;
430    }
431    
432    public boolean initialised() {
433            return initialised;
434    }
435    
436    /**
437     * Gets the application's global data file settings.
438     * 
439     * @return Global data file settings.
440     */
441    public DataFileSettings getDataFileSettings() {
442            return dataFileSettings;
443    }
444    
445    /**
446     * Gets the application's global graph settings.
447     * 
448     * @return Global graph settings.
449     */
450    public GraphSettings getGraphSettings() {
451            return graphSettings;
452    }
453    
454    
455    /**
456     * Gets the application's global data series settings.
457     * 
458     * @return Global data series settings.
459     */
460    public DataSeriesSettings getDataSeriesSettings() {
461            return seriesSettings;
462    }
463    
464    
465    /**
466     * Get the exporter that can be used to create image representations of LiveGraph plots.
467     * 
468     * @return An exporter that can be used to create image representations of LiveGraph plots.
469     */
470    public GraphExporter getGraphExporter() {
471            if (null == graphExporter) {
472                    Plotter plotter = new Plotter(dataCache);
473                    eventManager.registerListener(plotter);
474                    graphExporter = new GraphExporter(plotter);
475            }
476            return graphExporter;
477    }
478    
479    
480    
481    /**
482     * Installs an uncaught exception handler that logs errors to the message window
483     * as well as to the colsole.
484     */
485    private void installUncaughtExceptionHandler() {
486            
487            try {
488                    commonDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
489                    UncaughtExceptionHandler handler = new UncaughtExceptionHandler();
490                    Thread.setDefaultUncaughtExceptionHandler(handler);
491                    
492            } catch (SecurityException e) {
493                    commonDefaultUncaughtExceptionHandler = null;
494                    System.err.println("LiveGraph has no permission to install a custom exception handler."
495                                                     + " Will run without one.");           
496                    System.out.println("LiveGraph has no permission to install a custom exception handler."
497                                                     + " Will run without one.");
498            }
499    }
500    
501    
502    private class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
503            public void uncaughtException(Thread t, Throwable e) {
504                    synchronized(System.err) {
505                            System.err.println("Error in thread \"" + t.getName() + "\":");
506                            e.printStackTrace(System.err);
507                    }
508                    if (null != guiManager && null != updateInvoker) {
509                            String err = String.format("Error in thread \"%d\":%n%s",
510                                                                               t.getName(),
511                                                                               ThrowableTools.stackTraceToString(e));
512                            guiManager.logErrorLn(err);
513                    }
514        }
515    } // private class UncaughtExceptionHandler
516    
517    } // public class LiveGraph