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