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