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 "AS IS", 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