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