001 package org.LiveGraph; 002 003 import java.io.File; 004 import java.util.HashMap; 005 import java.util.Map; 006 007 import javax.swing.JOptionPane; 008 009 import org.LiveGraph.dataCache.DataCache; 010 import org.LiveGraph.dataCache.UpdateInvoker; 011 import org.LiveGraph.gui.DataFileSettingsWindow; 012 import org.LiveGraph.gui.GraphSettingsWindow; 013 import org.LiveGraph.gui.MainWindow; 014 import org.LiveGraph.gui.PlotWindow; 015 import org.LiveGraph.gui.SeriesSettingsWindow; 016 import org.LiveGraph.plot.GraphExporter; 017 import org.LiveGraph.plot.Plotter; 018 import org.LiveGraph.settings.DataFileSettings; 019 import org.LiveGraph.settings.DataSeriesSettings; 020 import org.LiveGraph.settings.ErrorWhileSettingHasChangedProcessingException; 021 import org.LiveGraph.settings.GraphSettings; 022 023 import com.softnetConsult.utils.exceptions.ThrowableTools; 024 025 026 /** 027 * This is the main executable class of the LiveGraph plotter application. 028 * An instance of this class represents the application itself. The tasks of this 029 * class is to interpret the command line parameters, to set-up and to start-up 030 * the GUI and the back-end of the application, and to provide some 031 * functions which are used by different modules of the application to communicate 032 * with each other and to access global data, such as settings. 033 * 034 * <p style="font-size:smaller;">This product includes software developed by the 035 * <strong>LiveGraph</strong> project and its contributors.<br /> 036 * (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br /> 037 * Copyright (c) 2007 G. Paperin.<br /> 038 * All rights reserved. 039 * </p> 040 * <p style="font-size:smaller;">File: LiveGraph.java</p> 041 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or 042 * without modification, are permitted provided that the following terms and conditions are met: 043 * </p> 044 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above 045 * acknowledgement of the LiveGraph project and its web-site, the above copyright notice, 046 * this list of conditions and the following disclaimer.<br /> 047 * 2. Redistributions in binary form must reproduce the above acknowledgement of the 048 * LiveGraph project and its web-site, the above copyright notice, this list of conditions 049 * and the following disclaimer in the documentation and/or other materials provided with 050 * the distribution.<br /> 051 * 3. All advertising materials mentioning features or use of this software or any derived 052 * software must display the following acknowledgement:<br /> 053 * <em>This product includes software developed by the LiveGraph project and its 054 * contributors.<br />(http://www.live-graph.org)</em><br /> 055 * 4. All advertising materials distributed in form of HTML pages or any other technology 056 * permitting active hyper-links that mention features or use of this software or any 057 * derived software must display the acknowledgment specified in condition 3 of this 058 * agreement, and in addition, include a visible and working hyper-link to the LiveGraph 059 * homepage (http://www.live-graph.org). 060 * </p> 061 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 062 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 063 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 064 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 065 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 066 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 067 * </p> 068 * 069 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>) 070 * @version {@value org.LiveGraph.LiveGraph#version} 071 */ 072 public class LiveGraph { 073 074 /** 075 * LiveGraph software version. 076 */ 077 public static final String version = "1.1.1"; 078 079 // Static stuff { 080 081 /** 082 * Singleton application instance. 083 */ 084 private static LiveGraph app = null; 085 086 /** 087 * Singleton application access method. 088 * 089 * @return The singleton application object. 090 */ 091 public static LiveGraph application() { 092 if (null == LiveGraph.app) 093 LiveGraph.app = new LiveGraph(); 094 return LiveGraph.app; 095 } 096 097 /** 098 * Program entry point. 099 * Creates an application instance and calls the {@link #exec(String[])} method. 100 * 101 * @param args Command line parameters. 102 */ 103 public static void main(String [] args) { 104 LiveGraph.application().exec(args); 105 } 106 107 // } end of static stuff. 108 109 110 /** 111 * Application's data update invoker. 112 */ 113 private UpdateInvoker updateInvoker = null; 114 115 116 /** 117 * Main GUI window of the application. 118 */ 119 private MainWindow mainWindow = null; 120 121 /** 122 * GUI window for data file settings. 123 */ 124 private DataFileSettingsWindow dataFileSettingsWindow = null; 125 126 /** 127 * GUI window for graph settings. 128 */ 129 private GraphSettingsWindow graphSettingsWindow = null; 130 131 /** 132 * GUI window for data series settings. 133 */ 134 private SeriesSettingsWindow seriesSettingsWindow = null; 135 136 /** 137 * GUI window for the actual graph plot. 138 */ 139 private PlotWindow plotWindow = null; 140 141 142 /** 143 * Holds the data file settings for the application. 144 */ 145 private DataFileSettings dataFileSettings = null; 146 147 /** 148 * Holds the graph settings for the application. 149 */ 150 private GraphSettings graphSettings = null; 151 152 /** 153 * Holds the data series settings for the application. 154 */ 155 private DataSeriesSettings seriesSettings = null; 156 157 /** 158 * Holds the graph exporter. 159 */ 160 private GraphExporter graphExporter = null; 161 162 163 /** 164 * Main program method. 165 * It parses the command line parameters, sets up the GUI and the back-end components 166 * of the application and configures the their communication. It then loads the default 167 * settings and passes the execution control to the main Swing GUI loop. 168 * 169 * @param args Command line arguments. 170 */ 171 public void exec(String [] args) { 172 173 // Setup exception handling: 174 installUncaughtExceptionHandler(); 175 176 // Buffer for names of the settings files to load automatically: 177 Map<String, String> startupFiles = new HashMap<String, String>(); 178 179 // Parse the command line arguments and combine the results with default settings: 180 String argsParseErrorMsg = getInitialSettingsFiles(args, startupFiles); 181 182 // Create settings holder objects: 183 dataFileSettings = new DataFileSettings(); 184 graphSettings = new GraphSettings(); 185 seriesSettings = new DataSeriesSettings(); 186 187 // Set-up communication between different settings objects: 188 graphSettings.addObserver(seriesSettings); 189 190 // Create the data cache and the data update invoker, 191 // set-up their communication with other objects, 192 // and create the data update invocation thread: 193 DataCache dataCache = new DataCache(); 194 updateInvoker = new UpdateInvoker(); 195 updateInvoker.setDataCache(dataCache); 196 dataFileSettings.addObserver(updateInvoker); 197 Thread fileUpdateInvokerThread = new Thread(updateInvoker, "Update invoker thread"); 198 199 // Create the graph plotter and set-up its communication with other objects: 200 Plotter mainPlotter = new Plotter(dataCache); 201 dataCache.addObserver(mainPlotter); 202 graphSettings.addObserver(mainPlotter); 203 seriesSettings.addObserver(mainPlotter); 204 205 // Create the graphExporter: 206 this.graphExporter = new GraphExporter(mainPlotter); 207 208 // Create the main application window: 209 mainWindow = new MainWindow(); 210 mainWindow.setVisible(true); 211 212 // Create the data file settings window and set-up its communication with other objects: 213 dataFileSettingsWindow = new DataFileSettingsWindow(); 214 setDisplayDataFileSettingsWindow(true); 215 updateInvoker.addObserver(dataFileSettingsWindow); 216 dataCache.addObserver(dataFileSettingsWindow); 217 dataFileSettings.addObserver(dataFileSettingsWindow); 218 219 // Create the graph settings window and set-up its communication with other objects: 220 graphSettingsWindow = new GraphSettingsWindow(); 221 setDisplayGraphSettingsWindow(true); 222 dataCache.addObserver(graphSettingsWindow); 223 graphSettings.addObserver(graphSettingsWindow); 224 225 // Create the data series settings window and set-up its communication with other objects: 226 seriesSettingsWindow = new SeriesSettingsWindow(); 227 setDisplaySeriesSettingsWindow(true); 228 dataCache.addObserver(seriesSettingsWindow); 229 seriesSettings.addObserver(seriesSettingsWindow); 230 graphSettings.addObserver(seriesSettingsWindow); 231 232 // Create the graph plot window and set-up its communication with other objects: 233 plotWindow = new PlotWindow(mainPlotter); 234 setDisplayPlotWindow(true); 235 plotWindow.addSeriesHighlightListeners(seriesSettingsWindow); 236 dataCache.addObserver(plotWindow); 237 seriesSettings.addObserver(plotWindow); 238 graphSettings.addObserver(plotWindow); 239 240 // Start the data update invocation thread: 241 fileUpdateInvokerThread.start(); 242 243 // Display any possible error messages about the command line arguments: 244 if (null != argsParseErrorMsg) 245 logErrorLn(argsParseErrorMsg); 246 247 // Load default or command-line specified graph settings: 248 if (startupFiles.containsKey("GraphSettings")) { 249 String fn = startupFiles.get("GraphSettings"); 250 try { 251 if (graphSettings.load(fn)) 252 logSuccessLn("Initial graph settings loaded from \"" + fn + "\"."); 253 else 254 logErrorLn("Error while loading graph settings from \"" + fn + "\"."); 255 } catch (ErrorWhileSettingHasChangedProcessingException e) { 256 logErrorLn("There was a problem while loading initial graph settings: \n" 257 + " " + (null != e.getCause() ? e.getCause().getMessage() : e.getMessage()) + "."); 258 } 259 } 260 261 // Load default or command-line specified data series settings: 262 if (startupFiles.containsKey("DataSeriesSettings")) { 263 String fn = startupFiles.get("DataSeriesSettings"); 264 try { 265 if (seriesSettings.load(fn)) 266 logSuccessLn("Initial data series settings loaded from \"" + fn + "\"."); 267 else 268 logErrorLn("Error while loading data series settings from \"" + fn + "\"."); 269 } catch (ErrorWhileSettingHasChangedProcessingException e) { 270 logErrorLn("There was a problem while loading initial data series settings: \n" 271 + " " + (null != e.getCause() ? e.getCause().getMessage() : e.getMessage()) + "."); 272 } 273 } 274 275 // Load default or command-line specified data file settings: 276 if (startupFiles.containsKey("DataFileSettings")) { 277 String fn = startupFiles.get("DataFileSettings"); 278 try { 279 if (dataFileSettings.load(fn)) 280 logSuccessLn("Initial data file settings loaded from \"" + fn + "\"."); 281 else 282 logErrorLn("Error while loading data file settings from \"" + fn + "\"."); 283 } catch (ErrorWhileSettingHasChangedProcessingException e) { 284 logErrorLn("There was a problem while loading initial data file settings: \n" 285 + " " + (null != e.getCause() ? e.getCause().getMessage() : e.getMessage()) + "."); 286 287 } 288 } 289 290 if (!runsCorrectJavaVersion()) { 291 JOptionPane.showMessageDialog(null, "The Java runtime environment you are using may not " 292 + "support all program features.\n\n" 293 + "LiveGraph is targeted for Java version 1.6 or later, " 294 + "however, it may run on earlier Java versions with a " 295 + "reduced feature set.\nNote that various error messages " 296 + "may be displayed when accessing the unsupported features.\n\n" 297 + "Your current Java version is " + getJavaSpecificationVersion(), 298 "Incompatible Java version", JOptionPane.WARNING_MESSAGE); 299 } 300 301 } // public void exec(String [] args) 302 303 /** 304 * Determines the current Java specification version. 305 * @return The current Java specification version or {@code "unknown"} if it could not be obtained. 306 */ 307 public String getJavaSpecificationVersion() { 308 try { 309 return System.getProperty("java.specification.version"); 310 } catch (Throwable e) { 311 return "unknown"; 312 } 313 } 314 315 /** 316 * Determines whether the currect Java version is appropriate. This is done based on the system 317 * property {@code java.specification.version}. Java version {@code 1.6} or higher is considered ok. 318 * @return Whether the currect Java version is appropriate. 319 */ 320 public boolean runsCorrectJavaVersion() { 321 322 String specVer = getJavaSpecificationVersion(); 323 324 if (specVer.equalsIgnoreCase("unknown")) 325 return false; 326 327 int p = specVer.indexOf("."); 328 if (0 > p) 329 return false; 330 331 int mainVer = Integer.parseInt(specVer.substring(0, p)); 332 if (1 > mainVer) 333 return false; 334 if (1 < mainVer) 335 return true; 336 337 if (specVer.length() - 1 <= p) 338 return false; 339 340 int subVer = Integer.parseInt(specVer.substring(p + 1, p + 2)); 341 if (6 > subVer) 342 return false; 343 344 return true; 345 } 346 347 /** 348 * Parses the command line arguments for file names for initial settings and combines the results 349 * with default settings file names. 350 * 351 * @param args Command line arguments. 352 * @param startupFiles A table to hold the results. 353 * @return {@code null} if no error occured or the error message if there was an error (e.g. incorrect 354 * command line arguments). 355 */ 356 private String getInitialSettingsFiles(String [] args, Map<String, String> startupFiles) { 357 358 String errMsg = getInitialSettingsFromCommandLine(args, startupFiles); 359 setDefaultInitialSettingsFiles(startupFiles); 360 return errMsg; 361 } // getInitialSettingsFiles 362 363 /** 364 * Parses the command line arguments for file names for initial settings. 365 * 366 * @param args Command line arguments. 367 * @param startupFiles A table to hold the results. 368 * @return {@code null} if no error occured or the error message if there was an error (e.g. incorrect 369 * command line arguments). 370 */ 371 private String getInitialSettingsFromCommandLine(String [] args, Map<String, String> startupFiles) { 372 373 if (0 != args.length && 2 != args.length && 4 != args.length && 6 != args.length) { 374 return "Command line parameters are invalid and were ignored. \n" 375 + " Legal command line arguments are as follows: \n" 376 + " > java edu.monash.LiveGraph.LiveGraph [-dfs \"{data file settings file}\"] \n" 377 + " [-gs \"{graph settings file}\"] \n" 378 + " [-dss \"{data series settings file}\"] \n" 379 + " This means the program expects either 0, 2, 4 or 6 command line arguments. \n" 380 + " However, " + args.length + " argument" + (1==args.length?" was":"s were") + " passed."; 381 } 382 383 String errMsg = ""; 384 String s = null; 385 386 if (2 <= args.length) { 387 s = tryParseCommandLineArgument(args[0], args[1], startupFiles); 388 if (null != s && 0 < errMsg.length()) 389 s += "\n"; 390 if (null != s) 391 errMsg += s; 392 } 393 394 if (4 <= args.length) { 395 s = tryParseCommandLineArgument(args[2], args[3], startupFiles); 396 if (null != s && 0 < errMsg.length()) 397 s += "\n"; 398 if (null != s) 399 errMsg += s; 400 } 401 402 if (6 <= args.length) { 403 s = tryParseCommandLineArgument(args[4], args[5], startupFiles); 404 if (null != s && 0 < errMsg.length()) 405 s += "\n"; 406 if (null != s) 407 errMsg += s; 408 } 409 410 if (0 == errMsg.length()) 411 return null; 412 413 return errMsg; 414 } // getInitialSettingsFromCommandLine 415 416 /** 417 * Processes two consecutive command line parameters. 418 * 419 * @param flag Flag specifier command line parameter. 420 * @param filename Filename specifier command line parameter. 421 * @param startupFiles A table to hold the results. 422 * @return {@code null} if no error occured or the error message if there was an error (e.g. incorrect 423 * command line arguments). 424 */ 425 private String tryParseCommandLineArgument(String flag, String filename, Map<String, String> startupFiles) { 426 427 if (flag.equalsIgnoreCase("-dfs")) { 428 File f = new File(filename); 429 if (!f.exists()) 430 return "Could not load the data file settings from the file specified on the command line " 431 + "because the file does not exist. \n" 432 + " (" + filename + ")"; 433 startupFiles.put("DataFileSettings", f.getAbsolutePath()); 434 return null; 435 } 436 437 if (flag.equalsIgnoreCase("-gs")) { 438 File f = new File(filename); 439 if (!f.exists()) 440 return "Could not load the graph settings from the file specified on the command line " 441 + "because the file does not exist. \n" 442 + " (" + filename + ")"; 443 startupFiles.put("GraphSettings", f.getAbsolutePath()); 444 return null; 445 } 446 447 if (flag.equalsIgnoreCase("-dss")) { 448 File f = new File(filename); 449 if (!f.exists()) 450 return "Could not load the graph settings from the file specified on the command line " 451 + "because the file does not exist. \n" 452 + " (" + filename + ")"; 453 startupFiles.put("DataSeriesSettings", f.getAbsolutePath()); 454 return null; 455 } 456 457 return "Invalid command line flag \"" + flag + "\" will be ignored. \n" 458 + " The subsequent command line argument \"" + filename + "\" will also be ignored."; 459 } // tryParseCommandLineArgument 460 461 462 /** 463 * If some settings were not specified to be loaded from a file via the command line, 464 * this method is used to specify default files for loading the settings, provided the 465 * defualt files exist. 466 * 467 * @param startupFiles A table to hold the results. 468 */ 469 private void setDefaultInitialSettingsFiles(Map<String, String> startupFiles) { 470 471 if (! startupFiles.containsKey("DataFileSettings")) { 472 File f = new File("session" + DataFileSettings.preferredFileExtension); 473 if (f.exists()) 474 startupFiles.put("DataFileSettings", f.getAbsolutePath()); 475 } 476 477 if (! startupFiles.containsKey("GraphSettings")) { 478 File f = new File("session" + GraphSettings.preferredFileExtension); 479 if (f.exists()) 480 startupFiles.put("GraphSettings", f.getAbsolutePath()); 481 } 482 483 if (! startupFiles.containsKey("DataSeriesSettings")) { 484 File f = new File("session" + DataSeriesSettings.preferredFileExtension); 485 if (f.exists()) 486 startupFiles.put("DataSeriesSettings", f.getAbsolutePath()); 487 } 488 } // setDefaultInitialSettingsFiles 489 490 /** 491 * Prints an info message to the main window message area. 492 * 493 * @param o The message. 494 */ 495 public void logInfoLn(Object o) { 496 mainWindow.logInfoLn(o.toString()); 497 } 498 499 /** 500 * Prints an error message to the main window message area. 501 * 502 * @param o The message. 503 */ 504 public void logErrorLn(Object o) { 505 mainWindow.logErrorLn(o.toString()); 506 } 507 508 /** 509 * Prints an success message to the main window message area. 510 * 511 * @param o The message. 512 */ 513 public void logSuccessLn(Object o) { 514 mainWindow.logSuccessLn(o.toString()); 515 } 516 517 /** 518 * Displays or hides the data file settings window. 519 * 520 * @param state Whether to display ({@code true}) or to hide ({@code false}). 521 */ 522 public void setDisplayDataFileSettingsWindow(boolean state) { 523 dataFileSettingsWindow.setVisible(state); 524 mainWindow.fileSettingsDisplayStateChanged(state); 525 } 526 527 /** 528 * Displays or hides the graph settings window. 529 * 530 * @param state Whether to display ({@code true}) or to hide ({@code false}). 531 */ 532 public void setDisplayGraphSettingsWindow(boolean state) { 533 graphSettingsWindow.setVisible(state); 534 mainWindow.graphSettingsDisplayStateChanged(state); 535 } 536 537 /** 538 * Displays or hides the data series settings window. 539 * 540 * @param state Whether to display ({@code true}) or to hide ({@code false}). 541 */ 542 public void setDisplaySeriesSettingsWindow(boolean state) { 543 seriesSettingsWindow.setVisible(state); 544 mainWindow.seriesSettingsDisplayStateChanged(state); 545 } 546 547 /** 548 * Displays or hides plot window. 549 * 550 * @param state Whether to display ({@code true}) or to hide ({@code false}). 551 */ 552 public void setDisplayPlotWindow(boolean state) { 553 plotWindow.setVisible(state); 554 mainWindow.plotDisplayStateChanged(state); 555 } 556 557 /** 558 * This method is called by the main window when it is closed. This method 559 * initiates the disposing of all windows and the data update invocation 560 * thread in order to correctly close the application and save all settings 561 * to default files. 562 */ 563 public void disposeGUIAndExit() { 564 mainWindow.dispose(); 565 dataFileSettingsWindow.dispose(); 566 graphSettingsWindow.dispose(); 567 seriesSettingsWindow.dispose(); 568 plotWindow.dispose(); 569 graphExporter.disposeInternalGUI(); 570 updateInvoker.setMustQuit(true); 571 572 dataFileSettings.save("session" + DataFileSettings.preferredFileExtension); 573 graphSettings.save("session" + GraphSettings.preferredFileExtension); 574 seriesSettings.save("session" + DataSeriesSettings.preferredFileExtension); 575 } 576 577 /** 578 * Gets the application's global data file settings. 579 * 580 * @return Global data file settings. 581 */ 582 public DataFileSettings getDataFileSettings() { 583 return dataFileSettings; 584 } 585 586 /** 587 * Gets the application's global graph settings. 588 * 589 * @return Global graph settings. 590 */ 591 public GraphSettings getGraphSettings() { 592 return graphSettings; 593 } 594 595 596 /** 597 * Gets the application's global data series settings. 598 * 599 * @return Global data series settings. 600 */ 601 public DataSeriesSettings getDataSeriesSettings() { 602 return seriesSettings; 603 } 604 605 public GraphExporter getGraphExporter() { 606 return graphExporter; 607 } 608 609 /** 610 * Causes the next data update. 611 */ 612 public void initiateDataUpdate() { 613 updateInvoker.update(); 614 } 615 616 private void installUncaughtExceptionHandler() { 617 618 try { 619 UncaughtExceptionHandler handler = new UncaughtExceptionHandler(); 620 Thread.setDefaultUncaughtExceptionHandler(handler); 621 622 } catch (SecurityException e) { 623 e.printStackTrace(); 624 } 625 } 626 627 private class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 628 public void uncaughtException(Thread t, Throwable e) { 629 try { 630 String err = ThrowableTools.stackTraceToString(e); 631 if (null == mainWindow) { 632 String h = "Error in thread \"" + t.getName() + "\""; 633 JOptionPane.showMessageDialog(null, err, h, JOptionPane.ERROR_MESSAGE); 634 } else { 635 logErrorLn(err); 636 } 637 } finally { 638 e.printStackTrace(); 639 } 640 //throw new Error(e); 641 } 642 } // private class UncaughtExceptionHandler 643 644 } // public class LiveGraph