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