001 /* 002 * $Id: McIDASV.java,v 1.123 2012/02/19 17:35:53 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2012 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030 031 package edu.wisc.ssec.mcidasv; 032 033 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 034 035 import static ucar.unidata.xml.XmlUtil.getAttribute; 036 037 import java.awt.Insets; 038 import java.awt.event.ActionEvent; 039 import java.awt.event.ActionListener; 040 import java.awt.geom.Rectangle2D; 041 import java.io.BufferedReader; 042 import java.io.File; 043 import java.io.FileOutputStream; 044 import java.io.FileReader; 045 import java.io.PrintStream; 046 import java.lang.reflect.Method; 047 import java.rmi.RemoteException; 048 import java.util.*; 049 050 import javax.swing.Icon; 051 import javax.swing.JButton; 052 import javax.swing.JCheckBox; 053 import javax.swing.JComponent; 054 import javax.swing.JDialog; 055 import javax.swing.JLabel; 056 import javax.swing.JOptionPane; 057 import javax.swing.SwingUtilities; 058 059 import edu.wisc.ssec.mcidasv.util.SystemState; 060 import org.bushe.swing.event.annotation.AnnotationProcessor; 061 import org.bushe.swing.event.annotation.EventSubscriber; 062 import org.slf4j.Logger; 063 import org.slf4j.LoggerFactory; 064 import org.w3c.dom.Element; 065 066 import visad.VisADException; 067 068 import ucar.unidata.data.DataManager; 069 import ucar.unidata.idv.ArgsManager; 070 import ucar.unidata.idv.ControlDescriptor; 071 import ucar.unidata.idv.IdvObjectStore; 072 import ucar.unidata.idv.IdvPersistenceManager; 073 import ucar.unidata.idv.IdvPreferenceManager; 074 import ucar.unidata.idv.IdvResourceManager; 075 import ucar.unidata.idv.IntegratedDataViewer; 076 import ucar.unidata.idv.PluginManager; 077 import ucar.unidata.idv.VMManager; 078 import ucar.unidata.idv.ViewDescriptor; 079 import ucar.unidata.idv.ViewManager; 080 import ucar.unidata.idv.chooser.IdvChooserManager; 081 import ucar.unidata.idv.ui.IdvUIManager; 082 import ucar.unidata.ui.colortable.ColorTableManager; 083 import ucar.unidata.util.FileManager; 084 import ucar.unidata.util.GuiUtils; 085 import ucar.unidata.util.IOUtil; 086 import ucar.unidata.util.LogUtil; 087 import ucar.unidata.util.Misc; 088 import ucar.unidata.xml.XmlDelegateImpl; 089 import ucar.unidata.xml.XmlEncoder; 090 import ucar.unidata.xml.XmlUtil; 091 import uk.org.lidalia.sysoutslf4j.context.LogLevel; 092 import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J; 093 094 import edu.wisc.ssec.mcidasv.chooser.McIdasChooserManager; 095 import edu.wisc.ssec.mcidasv.control.LambertAEA; 096 import edu.wisc.ssec.mcidasv.data.McvDataManager; 097 import edu.wisc.ssec.mcidasv.monitors.MonitorManager; 098 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 099 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryStatus; 100 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 101 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 102 import edu.wisc.ssec.mcidasv.servermanager.EntryStore; 103 import edu.wisc.ssec.mcidasv.servermanager.EntryTransforms; 104 import edu.wisc.ssec.mcidasv.servermanager.LocalAddeEntry; 105 import edu.wisc.ssec.mcidasv.servermanager.LocalAddeEntry.AddeFormat; 106 import edu.wisc.ssec.mcidasv.servermanager.RemoteAddeEntry; 107 import edu.wisc.ssec.mcidasv.servermanager.TabbedAddeManager; 108 import edu.wisc.ssec.mcidasv.startupmanager.StartupManager; 109 import edu.wisc.ssec.mcidasv.ui.LayerAnimationWindow; 110 import edu.wisc.ssec.mcidasv.ui.McIdasColorTableManager; 111 import edu.wisc.ssec.mcidasv.ui.UIManager; 112 import edu.wisc.ssec.mcidasv.util.Contract; 113 import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 114 import edu.wisc.ssec.mcidasv.util.WebBrowser; 115 116 @SuppressWarnings("unchecked") 117 public class McIDASV extends IntegratedDataViewer { 118 119 private static final Logger logger = LoggerFactory.getLogger(McIDASV.class); 120 121 /** 122 * Path to a {@literal "session"} file--it's created upon McIDAS-V 123 * starting and removed when McIDAS-V exits cleanly. This allows us to 124 * perform a primitive check to see if the current session has happened 125 * after a crash. 126 */ 127 private static String SESSION_FILE = getSessionFilePath(); 128 129 private static boolean cleanExit = true; 130 131 private static Date previousStart = null; 132 133 /** Set to true only if "-forceaqua" was found in the command line. */ 134 public static boolean useAquaLookAndFeel = false; 135 136 /** Points to the adde image defaults. */ 137 public static final IdvResourceManager.XmlIdvResource RSC_FRAMEDEFAULTS = 138 new IdvResourceManager.XmlIdvResource("idv.resource.framedefaults", 139 "McIDAS-X Frame Defaults"); 140 141 /** Points to the server definitions. */ 142 public static final IdvResourceManager.XmlIdvResource RSC_SERVERS = 143 new IdvResourceManager.XmlIdvResource("idv.resource.servers", 144 "Servers", "servers\\.xml$"); 145 146 /** Used to access McIDAS-V state in a static context. */ 147 private static McIDASV staticMcv; 148 149 /** Accessory in file save dialog */ 150 private JCheckBox overwriteDataCbx = 151 new JCheckBox("Change data paths", false); 152 153 /** The chooser manager */ 154 protected McIdasChooserManager chooserManager; 155 156 /** The http based monitor to dump stack traces and shutdown the IDV */ 157 private McIDASVMonitor mcvMonitor; 158 159 /** {@link MonitorManager} allows for relatively easy and efficient monitoring of various resources. */ 160 private final MonitorManager monitorManager = new MonitorManager(); 161 162 /** Actions passed into {@link #handleAction(String, Hashtable, boolean)}. */ 163 private final List<String> actions = new LinkedList<String>(); 164 165 private enum WarningResult { OK, CANCEL, SHOW, HIDE }; 166 167 private EntryStore addeEntries; 168 169 private TabbedAddeManager tabbedAddeManager = null; 170 171 /** 172 * Create the McIDASV with the given command line arguments. 173 * This constructor calls {@link IntegratedDataViewer#init()} 174 * 175 * @param args Command line arguments 176 * @exception VisADException from construction of VisAd objects 177 * @exception RemoteException from construction of VisAD objects 178 */ 179 public McIDASV(String[] args) throws VisADException, RemoteException { 180 super(args); 181 182 AnnotationProcessor.process(this); 183 184 staticMcv = this; 185 186 // Keep this code around for reference--this requires MacMenuManager.java and MRJToolkit. 187 // We use OSXAdapter instead now, but it is unclear which is the preferred method. 188 // Let's use the one that works. 189 // if (isMac()) { 190 // try { 191 // Object[] constructor_args = { this }; 192 // Class[] arglist = { McIDASV.class }; 193 // Class mac_class = Class.forName("edu.wisc.ssec.mcidasv.MacMenuManager"); 194 // Constructor new_one = mac_class.getConstructor(arglist); 195 // new_one.newInstance(constructor_args); 196 // } 197 // catch(Exception e) { 198 // System.out.println(e); 199 // } 200 // } 201 202 // Set up our application to respond to the Mac OS X application menu 203 registerForMacOSXEvents(); 204 205 // This doesn't always look good... but keep it here for future reference 206 // UIDefaults def = javax.swing.UIManager.getLookAndFeelDefaults(); 207 // Enumeration defkeys = def.keys(); 208 // while (defkeys.hasMoreElements()) { 209 // Object item = defkeys.nextElement(); 210 // if (item.toString().indexOf("selectionBackground") > 0) { 211 // def.put(item, Constants.MCV_BLUE); 212 // } 213 // } 214 215 // we're tired of the IDV's default missing image, so reset it 216 GuiUtils.MISSING_IMAGE = "/edu/wisc/ssec/mcidasv/resources/icons/toolbar/mcidasv-round22.png"; 217 218 this.init(); 219 } 220 221 // Generic registration with the Mac OS X application menu 222 // Checks the platform, then attempts to register with the Apple EAWT 223 // See OSXAdapter.java to see how this is done without directly referencing any Apple APIs 224 public void registerForMacOSXEvents() { 225 if (isMac()) { 226 try { 227 // Generate and register the OSXAdapter, passing it a hash of all the methods we wish to 228 // use as delegates for various com.apple.eawt.ApplicationListener methods 229 Class<?> thisClass = getClass(); 230 Class<?>[] args = (Class[])null; 231 OSXAdapter.setQuitHandler(this, thisClass.getDeclaredMethod("MacOSXQuit", args)); 232 OSXAdapter.setAboutHandler(this, thisClass.getDeclaredMethod("MacOSXAbout", args)); 233 OSXAdapter.setPreferencesHandler(this, thisClass.getDeclaredMethod("MacOSXPreferences", args)); 234 } catch (Exception e) { 235 logger.error("Error while loading the OSXAdapter", e); 236 } 237 } 238 } 239 240 public boolean MacOSXQuit() { 241 return quit(); 242 } 243 244 public void MacOSXAbout() { 245 getIdvUIManager().about(); 246 } 247 248 public void MacOSXPreferences() { 249 showPreferenceManager(); 250 } 251 252 /** 253 * Start up the McIDAS-V monitor server. This is an http server on the port defined 254 * by the property idv.monitorport (8788). It is only accessible to 127.0.0.1 (localhost) 255 */ 256 // TODO: we probably don't want our own copy of this in the long run... 257 // all we did was change "IDV" to "McIDAS-V" 258 @Override protected void startMonitor() { 259 if (mcvMonitor != null) { 260 return; 261 } 262 final String monitorPort = getProperty(PROP_MONITORPORT, ""); 263 if (monitorPort!=null && monitorPort.trim().length()>0 && !"none".equals(monitorPort.trim())) { 264 Misc.run(new Runnable() { 265 @Override public void run() { 266 try { 267 mcvMonitor = new McIDASVMonitor(McIDASV.this, Integer.parseInt(monitorPort)); 268 mcvMonitor.init(); 269 } catch (Exception exc) { 270 LogUtil.consoleMessage("Unable to start McIDAS-V monitor on port:" + monitorPort); 271 LogUtil.consoleMessage("Error:" + exc); 272 } 273 } 274 }); 275 } 276 } 277 278 /** 279 * Initializes a XML encoder with McIDAS-V specific XML delegates. 280 * 281 * @param encoder XML encoder that'll be dealing with persistence. 282 * @param forRead Not used as of yet. 283 */ 284 // TODO: if we ever get up past three or so XML delegates, I vote that we 285 // make our own version of VisADPersistence. 286 @Override protected void initEncoder(XmlEncoder encoder, boolean forRead) { 287 288 encoder.addDelegateForClass(LambertAEA.class, new XmlDelegateImpl() { 289 @Override public Element createElement(XmlEncoder e, Object o) { 290 LambertAEA projection = (LambertAEA)o; 291 Rectangle2D rect = projection.getDefaultMapArea(); 292 List args = Misc.newList(rect); 293 List types = Misc.newList(rect.getClass()); 294 return e.createObjectConstructorElement(o, args, types); 295 } 296 }); 297 298 // TODO(jon): ultra fashion makeover!! 299 encoder.addDelegateForClass(RemoteAddeEntry.class, new XmlDelegateImpl() { 300 @Override public Element createElement(XmlEncoder e, Object o) { 301 RemoteAddeEntry entry = (RemoteAddeEntry)o; 302 Element element = e.createObjectElement(o.getClass()); 303 element.setAttribute("address", entry.getAddress()); 304 element.setAttribute("group", entry.getGroup()); 305 element.setAttribute("username", entry.getAccount().getUsername()); 306 element.setAttribute("project", entry.getAccount().getProject()); 307 element.setAttribute("source", entry.getEntrySource().toString()); 308 element.setAttribute("type", entry.getEntryType().toString()); 309 element.setAttribute("validity", entry.getEntryValidity().toString()); 310 element.setAttribute("status", entry.getEntryStatus().toString()); 311 return element; 312 } 313 314 @Override public Object createObject(XmlEncoder e, Element element) { 315 String address = getAttribute(element, "address"); 316 String group = getAttribute(element, "group"); 317 String username = getAttribute(element, "username"); 318 String project = getAttribute(element, "project"); 319 String source = getAttribute(element, "source"); 320 String type = getAttribute(element, "type"); 321 String validity = getAttribute(element, "validity"); 322 String status = getAttribute(element, "status"); 323 324 EntrySource entrySource = EntryTransforms.strToEntrySource(source); 325 EntryType entryType = EntryTransforms.strToEntryType(type); 326 EntryValidity entryValidity = EntryTransforms.strToEntryValidity(validity); 327 EntryStatus entryStatus = EntryTransforms.strToEntryStatus(status); 328 329 RemoteAddeEntry entry = 330 new RemoteAddeEntry.Builder(address, group) 331 .account(username, project) 332 .source(entrySource) 333 .type(entryType) 334 .validity(entryValidity) 335 .status(entryStatus).build(); 336 337 return entry; 338 } 339 }); 340 341 encoder.addDelegateForClass(LocalAddeEntry.class, new XmlDelegateImpl() { 342 @Override public Element createElement(XmlEncoder e, Object o) { 343 LocalAddeEntry entry = (LocalAddeEntry)o; 344 Element element = e.createObjectElement(o.getClass()); 345 element.setAttribute("group", entry.getGroup()); 346 element.setAttribute("descriptor", entry.getDescriptor()); 347 element.setAttribute("realtime", entry.getRealtimeAsString()); 348 element.setAttribute("format", entry.getFormat().name()); 349 element.setAttribute("start", entry.getStart()); 350 element.setAttribute("end", entry.getEnd()); 351 element.setAttribute("fileMask", entry.getMask()); 352 element.setAttribute("name", entry.getName()); 353 element.setAttribute("status", entry.getEntryStatus().name()); 354 return element; 355 } 356 @Override public Object createObject(XmlEncoder e, Element element) { 357 String group = getAttribute(element, "group"); 358 String descriptor = getAttribute(element, "descriptor"); 359 String realtime = getAttribute(element, "realtime", ""); 360 AddeFormat format = EntryTransforms.strToAddeFormat(XmlUtil.getAttribute(element, "format")); 361 String start = getAttribute(element, "start", "1"); 362 String end = getAttribute(element, "end", "999999"); 363 String fileMask = getAttribute(element, "fileMask"); 364 String name = getAttribute(element, "name"); 365 String status = getAttribute(element, "status", "ENABLED"); 366 LocalAddeEntry.Builder builder = new LocalAddeEntry.Builder(name, group, fileMask, format).range(start, end).descriptor(descriptor).realtime(realtime).status(status); 367 return builder.build(); 368 } 369 }); 370 371 // encoder.addHighPriorityDelegateForClass(AddeImageInfo.class, new XmlDelegateImpl() { 372 // @Override public Element createElement(XmlEncoder e, Object o) { 373 // AddeImageInfo info = (AddeImageInfo)o; 374 // String user = info.getUser(); 375 // int proj = info.getProject(); 376 // logger.trace("user={} proj={}", new Object[] { user, proj }); 377 // return e.createElementDontCheckDelegate(o); 378 // } 379 // @Override public Object createObject(XmlEncoder e, Element element) { 380 // String host = getAttribute(element, "Host"); 381 // String group = getAttribute(element, "Group"); 382 // String descriptor = getAttribute(element, "Descriptor"); 383 // String type = getAttribute(element, "RequestType"); 384 // 385 // EntryStore store = getServerManager(); 386 // boolean mcservRunning = store.checkLocalServer(); 387 // boolean isKnown = store.searchWithPrefix(host+'!'+group).isEmpty(); 388 // 389 // logger.trace("isKnown={} host='{}' group='{}' type='{}' desc='{}'", new Object[] { isKnown, host, group, descriptor, type }); 390 // return e.createObjectDontCheckDelegate(element); 391 // } 392 // }); 393 394 // encoder.addHighPriorityDelegateForClass(AddeImageDescriptor.class, new XmlDelegateImpl() { 395 // @Override public Element createElement(XmlEncoder e, Object o) { 396 // AddeImageDescriptor desc = (AddeImageDescriptor)o; 397 // String source = desc.getSource(); 398 // desc.setSource(source.replace("USER", "user")); 399 // return desc.createElement(e, o); 400 // } 401 // @Override public Object createObject(XmlEncoder e, Element element) { 402 // 403 // return e.createObjectDontCheckDelegate(element); 404 // } 405 // }); 406 407 /** 408 * Move legacy classes to a new location 409 */ 410 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.Test2ImageDataSource", 411 "edu.wisc.ssec.mcidasv.data.adde.AddeImageParameterDataSource"); 412 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.Test2AddeImageDataSource", 413 "edu.wisc.ssec.mcidasv.data.adde.AddeImageParameterDataSource"); 414 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.AddePointDataSource", 415 "edu.wisc.ssec.mcidasv.data.adde.AddePointDataSource"); 416 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.AddeSoundingAdapter", 417 "edu.wisc.ssec.mcidasv.data.adde.AddeSoundingAdapter"); 418 } 419 420 /** 421 * Returns <i>all</i> of the actions used in this McIDAS-V session. This is 422 * possibly TMI and might be removed... 423 * 424 * @return Actions executed thus far. 425 */ 426 public List<String> getActionHistory() { 427 return actions; 428 } 429 430 /** 431 * Converts {@link ArgsManager#getOriginalArgs()} to a {@link List} and 432 * returns. 433 * 434 * @return The command-line arguments used to start McIDAS-V, as an 435 * {@code ArrayList}. 436 */ 437 public List<String> getCommandLineArgs() { 438 String[] originalArgs = getArgsManager().getOriginalArgs(); 439 List<String> args = arrList(originalArgs.length); 440 Collections.addAll(args, originalArgs); 441 return args; 442 } 443 444 /** 445 * Captures the action passed to {@code handleAction}. The action is logged 446 * and additionally, if the action is a HTML link, we attempt to visit the 447 * link in the user's preferred browser. 448 */ 449 @Override public boolean handleAction(String action, Hashtable properties, 450 boolean checkForAlias) 451 { 452 actions.add(action); 453 454 if (IOUtil.isHtmlFile(action)) { 455 WebBrowser.browse(action); 456 return true; 457 } 458 459 return super.handleAction(action, properties, checkForAlias); 460 } 461 462 /** 463 * This method checks if the given action is one of the following. 464 * <p> 465 * <li> Jython code -- starts with jython:<br> 466 * <li> Help link -- starts with help:<br> 467 * <li> Resource bundle file -- ends with .rbi<br> 468 * <li> bundle file -- ends with .xidv<br> 469 * <li> jnlp file -- ends with .jnlp<br> 470 * It returns true if the action is one of these. 471 * False otherwise. 472 * 473 * @param action The string action 474 * @param properties any properties 475 * @return Was this action handled 476 */ 477 @Override protected boolean handleFileOrUrlAction(String action, Hashtable properties) { 478 boolean result = false; 479 boolean idvAction = action.startsWith("idv:"); 480 boolean jythonAction = action.startsWith("jython:"); 481 482 if (!idvAction && !jythonAction) { 483 return super.handleFileOrUrlAction(action, properties); 484 } 485 486 Map<String, Object> hashProps; 487 if (properties != null) { 488 hashProps = new HashMap<String, Object>(properties); 489 } else { 490 //noinspection CollectionWithoutInitialCapacity 491 hashProps = new HashMap<String, Object>(); 492 } 493 494 ucar.unidata.idv.JythonManager jyManager = getJythonManager(); 495 if (idvAction) { 496 action = new String(action.replace("&", "&").substring(4)); 497 // getJythonManager().evaluateUntrusted(action, hashProps); 498 jyManager.evaluateUntrusted(action, hashProps); 499 result = true; 500 } else if (jythonAction) { 501 action = new String(action.substring(7)); 502 jyManager.evaluateTrusted(action, hashProps); 503 // ucar.unidata.idv.JythonManager jyMan = new ucar.unidata.idv.JythonManager(); 504 // jyMan 505 // getJythonManager().evaluateTrusted(action, hashProps); 506 result = true; 507 } else { 508 result = super.handleFileOrUrlAction(action, properties); 509 } 510 return result; 511 } 512 513 /** 514 * Add a new {@link ControlDescriptor} into the {@code controlDescriptor} 515 * list and {@code controlDescriptorMap}. 516 * 517 * <p>This method differs from the IDV's in that McIDAS-V <b>overwrites</b> 518 * existing {@code ControlDescriptor}s if 519 * {@link ControlDescriptor#getControlId()} matches. 520 * 521 * @param cd The ControlDescriptor to add. 522 * 523 * @throws NullPointerException if {@code cd} is {@code null}. 524 */ 525 @Override protected void addControlDescriptor(ControlDescriptor cd) { 526 Contract.notNull(cd, "Cannot add a null control descriptor to the list of control descriptors"); 527 String id = cd.getControlId(); 528 if (controlDescriptorMap.get(id) == null) { 529 controlDescriptors.add(cd); 530 controlDescriptorMap.put(id, cd); 531 } else { 532 for (int i = 0; i < controlDescriptors.size(); i++) { 533 ControlDescriptor tmp = (ControlDescriptor)controlDescriptors.get(i); 534 if (tmp.getControlId().equals(id)) { 535 controlDescriptors.set(i, cd); 536 controlDescriptorMap.put(id, cd); 537 break; 538 } 539 } 540 } 541 } 542 543 // pop up an incredibly rudimentary window that controls layer viz animation. 544 public void showLayerVisibilityAnimator() { 545 logger.trace("probably should try to do something here."); 546 SwingUtilities.invokeLater(new Runnable() { 547 public void run() { 548 try { 549 LayerAnimationWindow window = new LayerAnimationWindow(); 550 window.setVisible(true); 551 } catch (Exception e) { 552 logger.error("oh no! something happened!", e); 553 } 554 } 555 }); 556 } 557 558 /** 559 * Handles removing all loaded data sources. 560 * 561 * <p>If {@link ArgsManager#getIsOffScreen()} is {@code true}, this method 562 * will ignore the user's preferences and remove all data sources. 563 * 564 * @param showWarning Whether or not to display a warning message before 565 * removing <i>all</i> data sources. See the return details for more. 566 * 567 * @return Either {@code true} if the user wants to continue showing the 568 * warning dialog, or {@code false} if they've elected to stop showing the 569 * warning. If {@code showWarning} is {@code false}, this method will 570 * always return {@code false}, as the user isn't interested in seeing the 571 * warning. 572 */ 573 public boolean removeAllData(final boolean showWarning) { 574 boolean reallyRemove = false; 575 boolean continueWarning = true; 576 577 if (getArgsManager().getIsOffScreen()) { 578 super.removeAllDataSources(); 579 return continueWarning; 580 } 581 582 if (showWarning) { 583 Set<WarningResult> result = showWarningDialog( 584 "Confirm Data Removal", 585 "This action will remove all of the data currently loaded in McIDAS-V.<br>Is this what you want to do?", 586 Constants.PREF_CONFIRM_REMOVE_DATA, 587 "Always ask?", 588 "Remove all data", 589 "Do not remove any data"); 590 reallyRemove = result.contains(WarningResult.OK); 591 continueWarning = result.contains(WarningResult.SHOW); 592 } else { 593 // user doesn't want to see warning messages. 594 reallyRemove = true; 595 continueWarning = false; 596 } 597 598 if (reallyRemove) { 599 super.removeAllDataSources(); 600 } 601 602 return continueWarning; 603 } 604 605 /** 606 * Handles removing all loaded layers ({@literal "displays"} in IDV-land). 607 * 608 * <p>If {@link ArgsManager#getIsOffScreen()} is {@code true}, this method 609 * will ignore the user's preferences and remove all layers. 610 * 611 * @param showWarning Whether or not to display a warning message before 612 * removing <i>all</i> layers. See the return details for more. 613 * 614 * @return Either {@code true} if the user wants to continue showing the 615 * warning dialog, or {@code false} if they've elected to stop showing the 616 * warning. If {@code showWarning} is {@code false}, this method will 617 * always return {@code false}, as the user isn't interested in seeing the 618 * warning. 619 */ 620 public boolean removeAllLayers(final boolean showWarning) { 621 boolean reallyRemove = false; 622 boolean continueWarning = true; 623 624 if (getArgsManager().getIsOffScreen()) { 625 super.removeAllDisplays(); 626 ((ViewManagerManager)getVMManager()).disableAllLayerVizAnimations(); 627 return continueWarning; 628 } 629 630 if (showWarning) { 631 Set<WarningResult> result = showWarningDialog( 632 "Confirm Layer Removal", 633 "This action will remove every layer currently loaded in McIDAS-V.<br>Is this what you want to do?", 634 Constants.PREF_CONFIRM_REMOVE_LAYERS, 635 "Always ask?", 636 "Remove all layers", 637 "Do not remove any layers"); 638 reallyRemove = result.contains(WarningResult.OK); 639 continueWarning = result.contains(WarningResult.SHOW); 640 } else { 641 // user doesn't want to see warning messages. 642 reallyRemove = true; 643 continueWarning = false; 644 } 645 646 if (reallyRemove) { 647 super.removeAllDisplays(); 648 ((ViewManagerManager)getVMManager()).disableAllLayerVizAnimations(); 649 } 650 651 return continueWarning; 652 } 653 654 /** 655 * Overridden so that McIDAS-V can prompt the user before removing, if 656 * necessary. 657 */ 658 @Override public void removeAllDataSources() { 659 IdvObjectStore store = getStore(); 660 boolean showWarning = store.get(Constants.PREF_CONFIRM_REMOVE_DATA, true); 661 showWarning = removeAllData(showWarning); 662 store.put(Constants.PREF_CONFIRM_REMOVE_DATA, showWarning); 663 } 664 665 /** 666 * Overridden so that McIDAS-V can prompt the user before removing, if 667 * necessary. 668 */ 669 @Override public void removeAllDisplays() { 670 IdvObjectStore store = getStore(); 671 boolean showWarning = store.get(Constants.PREF_CONFIRM_REMOVE_LAYERS, true); 672 showWarning = removeAllLayers(showWarning); 673 store.put(Constants.PREF_CONFIRM_REMOVE_LAYERS, showWarning); 674 } 675 676 /** 677 * Handles removing all loaded layers ({@literal "displays"} in IDV-land) 678 * and data sources. 679 * 680 * <p>If {@link ArgsManager#getIsOffScreen()} is {@code true}, this method 681 * will ignore the user's preferences and remove all layers and data. 682 * 683 * @see #removeAllData(boolean) 684 * @see #removeAllLayers(boolean) 685 */ 686 public void removeAllLayersAndData() { 687 boolean reallyRemove = false; 688 boolean continueWarning = true; 689 690 if (getArgsManager().getIsOffScreen()) { 691 removeAllData(false); 692 removeAllLayers(false); 693 } 694 695 IdvObjectStore store = getStore(); 696 boolean showWarning = store.get(Constants.PREF_CONFIRM_REMOVE_BOTH, true); 697 if (showWarning) { 698 Set<WarningResult> result = showWarningDialog( 699 "Confirm Removal", 700 "This action will remove all of your currently loaded layers and data.<br>Is this what you want to do?", 701 Constants.PREF_CONFIRM_REMOVE_BOTH, 702 "Always ask?", 703 "Remove all layers and data", 704 "Do not remove anything"); 705 reallyRemove = result.contains(WarningResult.OK); 706 continueWarning = result.contains(WarningResult.SHOW); 707 } else { 708 // user doesn't want to see warning messages. 709 reallyRemove = true; 710 continueWarning = false; 711 } 712 713 // don't show the individual warning messages as the user has attempted 714 // to remove *both* 715 if (reallyRemove) { 716 removeAllData(false); 717 removeAllLayers(false); 718 } 719 720 store.put(Constants.PREF_CONFIRM_REMOVE_BOTH, continueWarning); 721 } 722 723 /** 724 * Helper method for showing the removal warning dialog. Note that none of 725 * these parameters should be {@code null} or empty. 726 * 727 * @param title Title of the warning dialog. 728 * @param message Contents of the warning. May contain HTML, but you do 729 * not need to provide opening and closing {@literal "html"} tags. 730 * @param prefId ID of the preference that controls whether or not the 731 * dialog should be displayed. 732 * @param prefLabel Brief description of the preference. 733 * @param okLabel Text of button that signals removal. 734 * @param cancelLabel Text of button that signals cancelling removal. 735 * 736 * @return A {@code Set} of {@link WarningResult}s that describes what the 737 * user opted to do. Should always contain only <b>two</b> elements. One 738 * for whether or not {@literal "ok"} or {@literal "cancel"} was clicked, 739 * and one for whether or not the warning should continue to be displayed. 740 */ 741 private Set<WarningResult> showWarningDialog(final String title, 742 final String message, final String prefId, final String prefLabel, 743 final String okLabel, final String cancelLabel) 744 { 745 JCheckBox box = new JCheckBox(prefLabel, true); 746 JComponent comp = GuiUtils.vbox( 747 new JLabel("<html>"+message+"</html>"), 748 GuiUtils.inset(box, new Insets(4, 15, 0, 10))); 749 750 Object[] options = { okLabel, cancelLabel }; 751 int result = JOptionPane.showOptionDialog( 752 LogUtil.getCurrentWindow(), // parent 753 comp, // msg 754 title, // title 755 JOptionPane.YES_NO_OPTION, // option type 756 JOptionPane.WARNING_MESSAGE, // message type 757 (Icon)null, // icon? 758 options, // selection values 759 options[1]); // initial? 760 761 WarningResult button = WarningResult.CANCEL; 762 if (result == JOptionPane.YES_OPTION) { 763 button = WarningResult.OK; 764 } 765 766 WarningResult show = WarningResult.HIDE; 767 if (box.isSelected()) { 768 show = WarningResult.SHOW; 769 } 770 771 return EnumSet.of(button, show); 772 } 773 774 public void removeTabData() { 775 } 776 777 public void removeTabLayers() { 778 779 } 780 781 public void removeTabLayersAndData() { 782 } 783 784 /** 785 * Overridden so that McIDAS-V doesn't have to create an entire new {@link ucar.unidata.idv.ui.IdvWindow} 786 * if {@link VMManager#findViewManager(ViewDescriptor)} can't find an appropriate 787 * ViewManager for {@code viewDescriptor}. 788 * 789 * <p>Not doing the above causes McIDAS-V to get stuck in a window creation 790 * loop. 791 */ 792 @Override public ViewManager getViewManager(ViewDescriptor viewDescriptor, 793 boolean newWindow, String properties) 794 { 795 ViewManager vm = 796 getVMManager().findOrCreateViewManager(viewDescriptor, properties); 797 if (vm == null) { 798 vm = super.getViewManager(viewDescriptor, newWindow, properties); 799 } 800 return vm; 801 } 802 803 /** 804 * Returns a reference to the current McIDAS-V object. Useful for working 805 * inside static methods. <b>Always check for null when using this 806 * method</b>. 807 * 808 * @return Either the current McIDAS-V "god object" or {@code null}. 809 */ 810 public static McIDASV getStaticMcv() { 811 return staticMcv; 812 } 813 814 /** 815 * @see ucar.unidata.idv.IdvBase#setIdv(ucar.unidata.idv.IntegratedDataViewer) 816 */ 817 @Override 818 public void setIdv(IntegratedDataViewer idv) { 819 this.idv = idv; 820 } 821 822 /** 823 * Load the McV properties. All other property files are disregarded. 824 * 825 * @see ucar.unidata.idv.IntegratedDataViewer#initPropertyFiles(java.util.List) 826 */ 827 @Override 828 public void initPropertyFiles(List files) { 829 files.clear(); 830 files.add(Constants.PROPERTIES_FILE); 831 } 832 833 /** 834 * Makes {@link PersistenceManager} save off a default {@literal "layout"} 835 * bundle. 836 */ 837 public void doSaveAsDefaultLayout() { 838 Misc.run(new Runnable() { 839 @Override public void run() { 840 ((PersistenceManager)getPersistenceManager()).doSaveAsDefaultLayout(); 841 } 842 }); 843 } 844 845 /** 846 * Determines whether or not a default layout exists. 847 * 848 * @return {@code true} if there is a default layout, {@code false} 849 * otherwise. 850 */ 851 public boolean hasDefaultLayout() { 852 String path = 853 getResourceManager().getResources(IdvResourceManager.RSC_BUNDLES) 854 .getWritable(); 855 return new File(path).exists(); 856 } 857 858 /** 859 * Called from the menu command to clear the default bundle. Overridden 860 * in McIDAS-V so that we reference the <i>layout</i> rather than the 861 * bundle. 862 */ 863 @Override public void doClearDefaults() { 864 if (GuiUtils.showYesNoDialog(null, 865 "Are you sure you want to delete your default layout?", 866 "Delete confirmation")) 867 resourceManager.clearDefaultBundles(); 868 } 869 870 /** 871 * <p> 872 * Overridden so that the support form becomes non-modal if launched from 873 * an exception dialog. 874 * </p> 875 * 876 * @see IntegratedDataViewer#addErrorButtons(JDialog, List, String, Throwable) 877 */ 878 @Override public void addErrorButtons(final JDialog dialog, 879 List buttonList, final String msg, final Throwable exc) 880 { 881 JButton supportBtn = new JButton("Support Form"); 882 supportBtn.addActionListener(new ActionListener() { 883 @Override public void actionPerformed(ActionEvent ae) { 884 getIdvUIManager().showSupportForm(msg, 885 LogUtil.getStackTrace(exc), null); 886 } 887 }); 888 buttonList.add(supportBtn); 889 } 890 891 /** 892 * Called after the IDV has finished setting everything up after starting. 893 * McIDAS-V is currently only using this method to determine if the last 894 * {@literal "exit"} was clean--whether or not {@code SESSION_FILE} was 895 * removed before the McIDAS-V process terminated. 896 */ 897 @Override public void initDone() { 898 super.initDone(); 899 GuiUtils.setApplicationTitle(""); 900 if (cleanExit || getArgsManager().getIsOffScreen()) { 901 return; 902 } 903 904 String msg = "The previous McIDAS-V session did not exit cleanly.<br>"+ 905 "Do you want to send the log file to the McIDAS Help Desk?"; 906 if (previousStart != null) { 907 msg = "The previous McIDAS-V session (start time: %s) did not exit cleanly.<br>"+ 908 "Do you want to send the log file to the McIDAS Help Desk?"; 909 msg = String.format(msg, previousStart); 910 } 911 912 boolean continueAsking = getStore().get("mcv.crash.boom.send.report", true); 913 if (!continueAsking) { 914 return; 915 } 916 917 Set<WarningResult> result = showWarningDialog( 918 "Report Crash", 919 msg, 920 "mcv.crash.boom.send.report", 921 "Always ask?", 922 "Open support form", 923 "Do not report"); 924 925 getStore().put("mcv.crash.boom.send.report", result.contains(WarningResult.SHOW)); 926 if (!result.contains(WarningResult.OK)) { 927 return; 928 } 929 930 getIdvUIManager().showSupportForm(); 931 } 932 933 /** 934 * @see IntegratedDataViewer#doOpen(String, boolean, boolean) 935 */ 936 @Override public void doOpen(final String filename, 937 final boolean checkUserPreference, final boolean andRemove) 938 { 939 doOpenInThread(filename, checkUserPreference, andRemove); 940 } 941 942 /** 943 * Have the user select an xidv file. If andRemove is true then we remove 944 * all data sources and displays. Then we open the unpersist the bundle in 945 * the xidv file 946 * 947 * @param filename The filename to open 948 * @param checkUserPreference Should we show, if needed, the Open dialog 949 * @param andRemove If true then first remove all data sources and displays 950 */ 951 private void doOpenInThread(String filename, boolean checkUserPreference, 952 boolean andRemove) 953 { 954 boolean overwriteData = false; 955 if (filename == null) { 956 if (overwriteDataCbx.getToolTipText() == null) { 957 overwriteDataCbx.setToolTipText("Change the file paths that the data sources use"); 958 } 959 960 filename = FileManager.getReadFile("Open File", 961 ((ArgumentManager)getArgsManager()).getBundleFilters(true), 962 GuiUtils.top(overwriteDataCbx)); 963 964 if (filename == null) { 965 return; 966 } 967 968 overwriteData = overwriteDataCbx.isSelected(); 969 } 970 971 if (ArgumentManager.isXmlBundle(filename)) { 972 getPersistenceManager().decodeXmlFile(filename, 973 checkUserPreference, overwriteData); 974 return; 975 } 976 handleAction(filename, null); 977 } 978 979 /** 980 * Make edu.wisc.ssec.mcidasv.JythonManager 981 * Factory method to create the 982 * {@link JythonManager} 983 * 984 * @return The jython manager 985 */ 986 @Override protected JythonManager doMakeJythonManager() { 987 // return (JythonManager) makeManager(JythonManager.class, 988 // new Object[] { idv }); 989 logger.debug("returning a new JythonManager"); 990 return new JythonManager(this); 991 } 992 993 /** 994 * Factory method to create the {@link IdvUIManager}. Here we create our 995 * own UI manager so it can do McV specific things. 996 * 997 * @return The UI manager indicated by the startup properties. 998 * 999 * @see ucar.unidata.idv.IdvBase#doMakeIdvUIManager() 1000 */ 1001 @Override 1002 protected IdvChooserManager doMakeIdvChooserManager() { 1003 chooserManager = (McIdasChooserManager)makeManager( 1004 McIdasChooserManager.class, new Object[] { this }); 1005 chooserManager.init(); 1006 return chooserManager; 1007 } 1008 1009 /** 1010 * Factory method to create the {@link IdvUIManager}. Here we create our 1011 * own UI manager so it can do McV specific things. 1012 * 1013 * @return The UI manager indicated by the startup properties. 1014 * 1015 * @see ucar.unidata.idv.IdvBase#doMakeIdvUIManager() 1016 */ 1017 @Override 1018 protected IdvUIManager doMakeIdvUIManager() { 1019 return new UIManager(this); 1020 } 1021 1022 /** 1023 * Create our own VMManager so that we can make the tabs play nice. 1024 * @see ucar.unidata.idv.IdvBase#doMakeVMManager() 1025 */ 1026 @Override 1027 protected VMManager doMakeVMManager() { 1028 // what an ugly class name :( 1029 return new ViewManagerManager(this); 1030 } 1031 1032 /** 1033 * Make the {@link McIdasPreferenceManager}. 1034 * @see ucar.unidata.idv.IdvBase#doMakePreferenceManager() 1035 */ 1036 @Override 1037 protected IdvPreferenceManager doMakePreferenceManager() { 1038 return new McIdasPreferenceManager(this); 1039 } 1040 1041 /** 1042 * <p>McIDAS-V (alpha 10+) needs to handle both IDV bundles without 1043 * component groups and all bundles from prior McV alphas. You better 1044 * believe we need to extend the persistence manager functionality!</p> 1045 * 1046 * @see ucar.unidata.idv.IdvBase#doMakePersistenceManager() 1047 */ 1048 @Override protected IdvPersistenceManager doMakePersistenceManager() { 1049 return new PersistenceManager(this); 1050 } 1051 1052 /** 1053 * Create, if needed, and return the {@link McIdasChooserManager}. 1054 * 1055 * @return The Chooser manager 1056 */ 1057 public McIdasChooserManager getMcIdasChooserManager() { 1058 return (McIdasChooserManager)getIdvChooserManager(); 1059 } 1060 1061 /** 1062 * Returns the {@link MonitorManager}. 1063 */ 1064 public MonitorManager getMonitorManager() { 1065 return monitorManager; 1066 } 1067 1068 /** 1069 * Responds to events generated by the server manager's GUI. Currently 1070 * limited to {@link TabbedAddeManager.Event#CLOSED}. 1071 */ 1072 @EventSubscriber(eventClass=TabbedAddeManager.Event.class) 1073 public void onServerManagerWindowEvent(TabbedAddeManager.Event evt) { 1074 if (evt == TabbedAddeManager.Event.CLOSED) { 1075 tabbedAddeManager = null; 1076 } 1077 } 1078 1079 /** 1080 * Creates (if needed) the server manager GUI and displays it. 1081 */ 1082 public void showServerManager() { 1083 if (tabbedAddeManager == null) { 1084 tabbedAddeManager = new TabbedAddeManager(getServerManager()); 1085 } 1086 tabbedAddeManager.showManager(); 1087 } 1088 1089 /** 1090 * Creates a new server manager (if needed) and returns it. 1091 */ 1092 public EntryStore getServerManager() { 1093 if (addeEntries == null) { 1094 addeEntries = new EntryStore(getStore(), getResourceManager()); 1095 addeEntries.startLocalServer(); 1096 } 1097 return addeEntries; 1098 } 1099 1100 public McvDataManager getMcvDataManager() { 1101 return (McvDataManager)getDataManager(); 1102 } 1103 1104 /** 1105 * Get McIDASV. 1106 * @see ucar.unidata.idv.IdvBase#getIdv() 1107 */ 1108 @Override public IntegratedDataViewer getIdv() { 1109 return this; 1110 } 1111 1112 /** 1113 * Creates a McIDAS-V argument manager so that McV can handle some non-IDV 1114 * command line things. 1115 * 1116 * @param args The arguments from the command line. 1117 * 1118 * @see ucar.unidata.idv.IdvBase#doMakeArgsManager(java.lang.String[]) 1119 */ 1120 @Override protected ArgsManager doMakeArgsManager(String[] args) { 1121 return new ArgumentManager(this, args); 1122 } 1123 1124 /** 1125 * Factory method to create the {@link McvDataManager}. 1126 * 1127 * @return The data manager 1128 * 1129 * @see ucar.unidata.idv.IdvBase#doMakeDataManager() 1130 */ 1131 @Override protected DataManager doMakeDataManager() { 1132 return new McvDataManager(this); 1133 } 1134 1135 /** 1136 * Make the McIDAS-V {@link StateManager}. 1137 * @see ucar.unidata.idv.IdvBase#doMakeStateManager() 1138 */ 1139 @Override protected StateManager doMakeStateManager() { 1140 return new StateManager(this); 1141 } 1142 1143 /** 1144 * Make the McIDAS-V {@link ResourceManager}. 1145 * @see ucar.unidata.idv.IdvBase#doMakeResourceManager() 1146 */ 1147 @Override protected IdvResourceManager doMakeResourceManager() { 1148 return new ResourceManager(this); 1149 } 1150 1151 /** 1152 * Make the {@link McIdasColorTableManager}. 1153 * @see ucar.unidata.idv.IdvBase#doMakeColorTableManager() 1154 */ 1155 @Override protected ColorTableManager doMakeColorTableManager() { 1156 return new McIdasColorTableManager(); 1157 } 1158 1159 /** 1160 * Factory method to create the {@link McvPluginManager}. 1161 * 1162 * @return The McV plugin manager. 1163 * 1164 * @see ucar.unidata.idv.IdvBase#doMakePluginManager() 1165 */ 1166 @Override protected PluginManager doMakePluginManager() { 1167 return new McvPluginManager(this); 1168 } 1169 1170 // /** 1171 // * Make the {@link edu.wisc.ssec.mcidasv.data.McIDASVProjectionManager}. 1172 // * @see ucar.unidata.idv.IdvBase#doMakeIdvProjectionManager() 1173 // */ 1174 // @Override 1175 // protected IdvProjectionManager doMakeIdvProjectionManager() { 1176 // return new McIDASVProjectionManager(this); 1177 // } 1178 1179 /** 1180 * Make a help button for a particular help topic 1181 * 1182 * @param helpId the topic id 1183 * @param toolTip the tooltip 1184 * 1185 * @return the button 1186 */ 1187 @Override public JComponent makeHelpButton(String helpId, String toolTip) { 1188 JButton btn = McVGuiUtils.makeImageButton(Constants.ICON_HELP, 1189 getIdvUIManager(), "showHelp", helpId, "Show help"); 1190 1191 if (toolTip != null) { 1192 btn.setToolTipText(toolTip); 1193 } 1194 return btn; 1195 } 1196 1197 /** 1198 * Return the current {@literal "userpath"} 1199 */ 1200 public String getUserDirectory() { 1201 return StartupManager.INSTANCE.getPlatform().getUserDirectory(); 1202 } 1203 1204 /** 1205 * Return the path to a file within {@literal "userpath"} 1206 */ 1207 public String getUserFile(String filename) { 1208 return StartupManager.INSTANCE.getPlatform().getUserFile(filename); 1209 } 1210 1211 /** 1212 * Invokes the main method for a given class. 1213 * 1214 * <p>Note: this is rather limited so far as it doesn't pass in any arguments. 1215 * 1216 * @param className Class whose main method is to be invoked. Cannot be {@code null}. 1217 */ 1218 public void runPluginMainMethod(final String className) { 1219 final String[] dummyArgs = { "" }; 1220 try { 1221 Class<?> clazz = Misc.findClass(className); 1222 Method mainMethod = Misc.findMethod(clazz, "main", new Class[] { dummyArgs.getClass() }); 1223 if (mainMethod != null) { 1224 mainMethod.invoke(null, new Object[] { dummyArgs }); 1225 } 1226 } catch (Exception e) { 1227 logger.error("problem with plugin class", e); 1228 LogUtil.logException("problem running main method for class: "+className, e); 1229 } 1230 } 1231 1232 /** 1233 * Attempts to determine if a given string is a 1234 * {@literal "loopback address"} (aka localhost). 1235 * 1236 * <p>Strings are <b>trimmed and converted to lowercase</b>, and currently 1237 * checked against: 1238 * <ul> 1239 * <li>{@code 127.0.0.1}</li> 1240 * <li>{@code ::1} (for IPv6)</li> 1241 * <li>Strings starting with {@code localhost}.</li> 1242 * </ul> 1243 * 1244 * @param host {@code String} to check. Should not be {@code null}. 1245 * 1246 * @return {@code true} if {@code host} is a recognized loopback address. 1247 * {@code false} otherwise. 1248 * 1249 * @throws NullPointerException if {@code host} is {@code null}. 1250 */ 1251 public static boolean isLoopback(final String host) { 1252 String cleaned = Contract.notNull(host.trim().toLowerCase()); 1253 return "127.0.0.1".startsWith(cleaned) 1254 || "::1".startsWith(cleaned) 1255 || cleaned.startsWith("localhost"); 1256 } 1257 1258 /** 1259 * Are we on a Mac? Used to build the MRJ handlers 1260 * Take from TN2042 1261 * @return 1262 */ 1263 public static boolean isMac() { 1264 return System.getProperty("mrj.version") != null; 1265 } 1266 1267 /** 1268 * Queries the {@code os.name} system property and if the result does not 1269 * start with {@literal "Windows"}, the platform is assumed to be 1270 * {@literal "unix-like"}. 1271 * 1272 * <p>Given the McIDAS-V supported platforms (Windows, {@literal "Unix"}, 1273 * and OS X), the above logic is safe. 1274 * 1275 * @return {@code true} if we're not running on Windows, {@code false} 1276 * otherwise. 1277 * 1278 * @throws RuntimeException if there is no property associated with 1279 * {@code os.name}. 1280 */ 1281 public static boolean isUnixLike() { 1282 String osName = System.getProperty("os.name"); 1283 if (osName == null) { 1284 throw new RuntimeException("no os.name system property!"); 1285 } 1286 1287 if (System.getProperty("os.name").startsWith("Windows")) { 1288 return false; 1289 } 1290 return true; 1291 } 1292 1293 /** 1294 * Queries the {@code os.name} system property and if the result starts 1295 * with {@literal "Windows"}, the platform is assumed to be Windows. Duh. 1296 * 1297 * @return {@code true} if we're running on Windows, {@code false} 1298 * otherwise. 1299 * 1300 * @throws RuntimeException if there is no property associated with 1301 * {@code os.name}. 1302 */ 1303 public static boolean isWindows() { 1304 String osName = System.getProperty("os.name"); 1305 if (osName == null) { 1306 throw new RuntimeException("no os.name system property!"); 1307 } 1308 1309 return osName.startsWith("Windows"); 1310 } 1311 1312 /** 1313 * If McIDAS-V is running on Windows, this method will return a 1314 * {@code String} that looks like {@literal "C:"} or {@literal "D:"}, etc. 1315 * 1316 * <p>If McIDAS-V is not running on Windows, this method will return an 1317 * empty {@code String}. 1318 * 1319 * @return Either the {@literal "drive letter"} of the {@code java.home} 1320 * property or an empty {@code String} if McIDAS-V isn't running on Windows. 1321 * 1322 * @throws RuntimeException if there is no property associated with 1323 * {@code java.home}. 1324 */ 1325 public static String getJavaDriveLetter() { 1326 if (!isWindows()) { 1327 return ""; 1328 } 1329 1330 String home = System.getProperty("java.home"); 1331 if (home == null) { 1332 throw new RuntimeException("no java.home system property!"); 1333 } 1334 1335 return home.substring(0, 2); 1336 } 1337 1338 /** 1339 * Attempts to create a {@literal "session"} file. This method will create 1340 * a {@literal "userpath"} if it does not already exist. 1341 * 1342 * @param path Path of the session file that should get created. 1343 * {@code null} values are not allowed, and sufficient priviledges are 1344 * assumed. 1345 * 1346 * @throws AssertionError if McIDAS-V couldn't write to {@code path}. 1347 * 1348 * @see #SESSION_FILE 1349 * @see #hadCleanExit(String) 1350 * @see #removeSessionFile(String) 1351 */ 1352 private static void createSessionFile(final String path) { 1353 assert path != null : "Cannot create a null path"; 1354 FileOutputStream out; 1355 PrintStream p; 1356 1357 File dir = new File(StartupManager.INSTANCE.getPlatform().getUserDirectory()); 1358 if (!dir.exists()) { 1359 dir.mkdir(); 1360 } 1361 1362 try { 1363 out = new FileOutputStream(path); 1364 p = new PrintStream(out); 1365 p.println(new Date().getTime()); 1366 p.close(); 1367 } catch (Exception e) { 1368 throw new AssertionError("Could not write to "+path+". Error message: "+e.getMessage()); 1369 } 1370 } 1371 1372 /** 1373 * Attempts to extract a timestamp from {@code path}. {@code path} is 1374 * expected to <b>only</b> contain a single line consisting of a 1375 * {@link Long} integer. 1376 * 1377 * @param path Path to the file of interest. 1378 * 1379 * @return Either a {@link Date} of the timestamp contained in 1380 * {@code path} or {@code null} if the extraction failed. 1381 */ 1382 private static Date extractDate(final String path) { 1383 assert path != null; 1384 Date savedDate = null; 1385 try { 1386 BufferedReader reader = new BufferedReader(new FileReader(path)); 1387 String line = reader.readLine(); 1388 if (line != null) { 1389 savedDate = new Date(Long.parseLong(line.trim())); 1390 } 1391 } catch (Exception e) { 1392 logger.trace("problem extracting the date!", e); 1393 } 1394 return savedDate; 1395 } 1396 1397 /** 1398 * Attempts to remove the file accessible via {@code path}. 1399 * 1400 * @param path Path of the file that'll get removed. This should be 1401 * non-null and point to an existing and writable filename (not a 1402 * directory). 1403 * 1404 * @throws AssertionError if the file at {@code path} could not be 1405 * removed. 1406 * 1407 * @see #SESSION_FILE 1408 * @see #createSessionFile(String) 1409 * @see #hadCleanExit(String) 1410 */ 1411 private static void removeSessionFile(final String path) { 1412 if (path == null) { 1413 return; 1414 } 1415 1416 File f = new File(path); 1417 1418 if (!f.exists() || !f.canWrite() || f.isDirectory()) { 1419 return; 1420 } 1421 1422 if (!f.delete()) { 1423 throw new AssertionError("Could not delete session file"); 1424 } 1425 } 1426 1427 /** 1428 * Tries to determine whether or not the last McIDAS-V session ended 1429 * {@literal "cleanly"}. Currently a simple check for a 1430 * {@literal "session"} file that is created upon starting and removed upon 1431 * ending. 1432 * 1433 * @param path Path to the session file to check. Can't be {@code null}. 1434 * 1435 * @return Either {@code true} if the file pointed at by {@code path} does 1436 * <b><i>NOT</i></b> exist, {@code false} if it does exist. 1437 * 1438 * @see #SESSION_FILE 1439 * @see #createSessionFile(String) 1440 * @see #removeSessionFile(String) 1441 */ 1442 private static boolean hadCleanExit(final String path) { 1443 assert path != null : "Cannot test for a null path"; 1444 return !(new File(path).exists()); 1445 } 1446 1447 /** 1448 * Returns the (<i>current</i>) path to the session file. Note that the 1449 * location of the file may change arbitrarily. 1450 * 1451 * @return {@code String} pointing to the session file. 1452 * 1453 * @see #SESSION_FILE 1454 */ 1455 public static String getSessionFilePath() { 1456 return StartupManager.INSTANCE.getPlatform().getUserFile("session.tmp"); 1457 } 1458 1459 /** 1460 * Useful for providing the startup manager with values other than the 1461 * defaults... Note that this method attempts to update the value of 1462 * {@link #SESSION_FILE}. 1463 * 1464 * @param args Likely the argument array coming from the main method. 1465 */ 1466 private static void applyArgs(final String[] args) { 1467 assert args != null : "Cannot use a null argument array"; 1468 StartupManager.applyArgs(true, false, args); 1469 SESSION_FILE = getSessionFilePath(); 1470 } 1471 1472 /** 1473 * The main. Configure the logging and create the McIdasV 1474 * 1475 * @param args Command line arguments 1476 * 1477 * @throws Exception When something untoward happens 1478 */ 1479 public static void main(String[] args) throws Exception { 1480 // SysOutOverSLF4J.registerLoggingSystem("ch.qos.logback"); 1481 SysOutOverSLF4J.sendSystemOutAndErrToSLF4J(LogLevel.INFO, LogLevel.WARN); 1482 logger.info("============================================================================="); 1483 logger.info("Starting McIDAS-V @ {}", new Date()); 1484 logger.info("Versions:"); 1485 logger.info("{}", SystemState.getMcvVersionString()); 1486 logger.info("{}", SystemState.getIdvVersionString()); 1487 long sysMem = Long.valueOf(SystemState.queryOpSysProps().get("opsys.memory.physical.total")); 1488 logger.info("{} MB system memory", Math.round(sysMem/1024/1024)); 1489 applyArgs(args); 1490 if (!hadCleanExit(SESSION_FILE)) { 1491 previousStart = extractDate(SESSION_FILE); 1492 } 1493 createSessionFile(SESSION_FILE); 1494 LogUtil.configure(); 1495 McIDASV myself = new McIDASV(args); 1496 } 1497 1498 /** 1499 * Attempts a clean shutdown of McIDAS-V. Currently this entails 1500 * suppressing any error dialogs, explicitly killing the 1501 * {@link #addeEntries}, and removing {@link #SESSION_FILE}. 1502 * 1503 * @param exitCode System exit code to use 1504 * 1505 * @see IntegratedDataViewer#quit() 1506 */ 1507 @Override protected void exit(int exitCode) { 1508 LogUtil.setShowErrorsInGui(false); 1509 1510 if (addeEntries != null) { 1511 addeEntries.saveEntries(); 1512 addeEntries.stopLocalServer(); 1513 } 1514 1515 removeSessionFile(SESSION_FILE); 1516 logger.info("Exiting McIDAS-V @ {}", new Date()); 1517 System.exit(exitCode); 1518 } 1519 1520 /** 1521 * Exposes {@link #exit(int)} to other classes. 1522 * 1523 * @param exitCode 1524 * 1525 * @see #exit(int) 1526 */ 1527 public void exitMcIDASV(int exitCode) { 1528 exit(exitCode); 1529 } 1530 }