001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2018
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
029package edu.wisc.ssec.mcidasv;
030
031
032import java.io.BufferedReader;
033import java.io.BufferedWriter;
034import java.io.File;
035import java.io.FileReader;
036import java.io.FileWriter;
037
038import java.util.Hashtable;
039import java.util.Map;
040import java.util.Properties;
041
042import java.awt.event.ActionEvent;
043
044import javax.swing.JEditorPane;
045import javax.swing.JLabel;
046import javax.swing.JOptionPane;
047import javax.swing.JPanel;
048import javax.swing.event.HyperlinkEvent;
049import javax.swing.event.HyperlinkListener;
050
051import ucar.unidata.idv.IdvObjectStore;
052import ucar.unidata.idv.IntegratedDataViewer;
053import ucar.unidata.util.FileManager;
054import ucar.unidata.util.IOUtil;
055import ucar.unidata.util.LogUtil;
056import ucar.unidata.util.Misc;
057import ucar.unidata.util.StringUtil;
058
059import org.slf4j.Logger;
060import org.slf4j.LoggerFactory;
061
062import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
063import edu.wisc.ssec.mcidasv.util.SystemState;
064
065/**
066 * This class is used to initialize McIDAS-V.
067 *
068 * <p>The initialization process includes creating an
069 * {@literal "object store"} (for preferences), user-modifiable settings,
070 * and so on.</p>
071 *
072 * <p>McIDAS-V uses this class to perform pretty much all version-check
073 * operations.</p>
074 */
075public class StateManager extends ucar.unidata.idv.StateManager
076    implements Constants, HyperlinkListener
077{
078    /** Logging object. */
079    private static final Logger logger =
080        LoggerFactory.getLogger(StateManager.class);
081        
082    /** Error message shown when given userpath cannot be used. */
083    public static final String USERPATH_IS_BAD_MESSAGE =
084        "<html>McIDAS-V is unable to create or write to the local user's " +
085        "directory.<br>Please select a directory.</html>";
086        
087    /** Message shown when asking the user to select a userpath. */
088    public static final String USERPATH_PICK =
089        "Please select a directory to use as the McIDAS-V user path.";
090        
091    /**
092     * Lazily-loaded VisAD build date. Value may be {@code null}.
093     *
094     * @see #getVisadDate()
095     */
096    private String visadDate;
097    
098    /**
099     * Lazily-loaded VisAD revision number. Value may be {@code null}.
100     *
101     * @see #getVisadVersion()
102     */
103    private String visadVersion;
104    
105    /**
106     * Lazily-loaded {@code ncIdv.jar} build timestamp. Value may be
107     * {@code null}.
108     *
109     * @see #getNetcdfDate()
110     */
111    private String netcdfDate;
112    
113    /**
114     * Lazily-loaded {@code ncIdv.jar} version. Value may be {@code null}.
115     *
116     * @see #getNetcdfVersion()
117     */
118    private String netcdfVersion;
119    
120    /**
121     * Lazily-loaded {@code mcidasv.jar} version. Value may be {@code null}.
122     *
123     * @see #getMcIdasVersion()
124     */
125    private String version;
126    
127    public StateManager(IntegratedDataViewer idv) {
128        super(idv);
129    }
130    
131    /**
132     * Override to set the right user directory.
133     *
134     * @return Newly created object store.
135     */
136    @Override protected IdvObjectStore doMakeObjectStore() {
137        IdvObjectStore store = new IdvObjectStore(getIdv(),
138                                   getStoreSystemName(), getStoreName(),
139                                   getIdv().getEncoderForRead(),
140                                   StartupManager.getInstance().getPlatform().getUserDirectory());
141        initObjectStore(store);
142        return store;
143    }
144    
145    /**
146     * Initialize the given object store. This mostly initializes the user's
147     * {@literal "userpath"} directory when it is first created.
148     *
149     * @param store Object store to initialize. Cannot be {@code null}.
150     */
151    @Override protected void initObjectStore(IdvObjectStore store) {
152        while (!store.userDirectoryOk()) {
153            LogUtil.userMessage(USERPATH_IS_BAD_MESSAGE);
154            File dir = FileManager.getDirectory(null, USERPATH_PICK);
155            if (dir != null) {
156                store.setOverrideDirectory(dir);
157            } else {
158                // TODO(jon): figure out why we aren't using regular exit stuff
159                System.exit(0);
160            }
161        }
162        
163        if (store.getMadeUserDirectory()) {
164            initNewUserDirectory(store.getUserDirectory());
165        }
166        initUserDirectory(store.getUserDirectory());
167    }
168    
169    /**
170     * Initialize the McIDAS-V user directory (if it is not already
171     * initalized).
172     *
173     * <p>Here, initialization means {@literal "the user directory exists,
174     * and contains a barebones version of mcidasv.rbi"}.</p>
175     *
176     * @param directory McIDAS-V user directory. Cannot be {@code null}.
177     */
178    @Override protected void initUserDirectory(File directory) {
179        File idvRbi = new File(IOUtil.joinDir(directory, "idv.rbi"));
180        if (idvRbi.exists()) {
181            if (!idvRbi.delete()) {
182                logger.warn("Could not delete '"+idvRbi+'\'');
183            }
184        }
185        File rbiFile = new File(IOUtil.joinDir(directory, "mcidasv.rbi"));
186        if (!rbiFile.exists()) {
187            String defaultRbi =
188                IOUtil.readContents(
189                    "/edu/wisc/ssec/mcidasv/resources/userrbi.rbi",
190                    (String)null);
191            if (defaultRbi != null) {
192                try {
193                    IOUtil.writeFile(rbiFile, defaultRbi);
194                } catch (Exception exc) {
195                    logException("Writing default rbi", exc);
196                }
197            }
198        }
199    }
200    
201    /**
202     * Handle a change to a link.
203     *
204     * @param e Link event. Cannot be {@code null}.
205     */
206    @Override public void hyperlinkUpdate(HyperlinkEvent e) {
207        if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
208            if (e.getURL() == null) {
209                click(e.getDescription());
210            } else {
211                click(e.getURL().toString());
212            }
213        }
214    }
215    
216    /**
217     * Handle a click on a link.
218     *
219     * @param url Link to visit.
220     */
221    public void click(String url) {
222        getIdv().actionPerformed(new ActionEvent(this, 0, url));
223    }
224    
225    /**
226     * Get the name of the current operating system (via
227     * {@literal "os.name"} system property).
228     *
229     * <p>Note: all space characters will be replaced with underscores.</p>
230     *
231     * @return Operating system name.
232     */
233    public String getOSName() {
234        String os = System.getProperty("os.name");
235        os = os.replaceAll(" ", "_");
236        return os;
237    }
238    
239    public String getMcIdasVersionAbout() {
240        if (version == null) {
241            getMcIdasVersion();
242        }
243        
244        String aboutText = (String)getProperty(Constants.PROP_ABOUTTEXT);
245        String versionAbout = IOUtil.readContents(aboutText, "");
246        versionAbout = StringUtil.replace(versionAbout, MACRO_VERSION, version);
247        Properties props = Misc.readProperties(
248            (String) getProperty(Constants.PROP_VERSIONFILE), 
249            null, 
250            getClass()
251        );
252        
253        String value = getIdvVersion();
254        versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_IDV_VERSION, value);
255        value = props.getProperty(PROP_COPYRIGHT_YEAR, "");
256        versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_COPYRIGHT_YEAR, value);
257        value = props.getProperty(PROP_BUILD_DATE, "Unknown");
258        versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_BUILDDATE, value);
259        versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_VISAD_VERSION, getVisadVersion());
260        
261        return versionAbout;
262    }
263    
264    public String getMcIdasVersion() {
265        if (version != null) {
266            return version;
267        }
268        
269        Properties props = new Properties();
270        props = Misc.readProperties((String) getProperty(Constants.PROP_VERSIONFILE), null, getClass());
271        String maj = props.getProperty(PROP_VERSION_MAJOR, "0");
272        String min = props.getProperty(PROP_VERSION_MINOR, "0");
273        String rel = props.getProperty(PROP_VERSION_RELEASE, "");
274        
275        version = maj.concat(".").concat(min).concat(rel);
276        
277        return version;
278    }
279    
280    /**
281     * Returns the current Jython version.
282     * 
283     * @return Jython's version information.
284     */
285    @Override public String getJythonVersion() {
286        return org.python.Version.PY_VERSION;
287    }
288    
289    /**
290     * Get a property.
291     *
292     * @param name Name of the property. Cannot be {@code null}.
293     *
294     * @return Value associated with {@code name} or {@code null}.
295     */
296    @Override public Object getProperty(final String name) {
297        Object value = null;
298        if (McIDASV.isMac()) {
299            value = getProperties().get("mac."+name);
300        }
301        if (value == null) {
302            value = getProperties().get(name);
303        }
304        if (value == null) {
305            String fixedName = StateManager.fixIds(name);
306            if (!name.equals(fixedName)) {
307                return getProperties().get(fixedName);
308            }
309        }
310        return value;
311    }
312    
313    /**
314     * Find the value associated with the given ID by checking the
315     * {@literal "properties"}, and if nothing was found, check the preferences.
316     *
317     * @param name Property or preference ID. Cannot be {@code null}.
318     *
319     * @return Either the value associated with {@code name} or {@code null}.
320     */
321    public Object getPropertyOrPreference(String name) {
322        Object o = getProperty(name);
323        if (o == null) {
324            o = getPreference(name);
325        }
326        return o;
327    }
328    
329    /**
330     * Find the {@link String} value associated with the given ID by checking
331     * the {@literal "properties"}, and if nothing was found, check the
332     * preferences.
333     *
334     * @param name Property or preference ID. Cannot be {@code null}.
335     * @param dflt Value to return if there is no property or preference
336     * associated with {@code name}
337     *
338     * @return Either the value associated with {@code name} or {@code dflt}.
339     */
340    public String getPropertyOrPreference(String name, String dflt) {
341        String value = dflt;
342        Object o = getPropertyOrPreference(name);
343        if (o != null) {
344            value = o.toString();
345        }
346        return value;
347    }
348    
349    /**
350     * Find the {@link Integer} value associated with the given ID by checking
351     * the {@literal "properties"}, and if nothing was found, check the
352     * preferences.
353     *
354     * @param name Property or preference ID. Cannot be {@code null}.
355     * @param dflt Value to return if there is no property or preference
356     * associated with {@code name}
357     *
358     * @return Either the value associated with {@code name} or {@code dflt}.
359     */
360    public int getPropertyOrPreference(String name, int dflt) {
361        int value = dflt;
362        Object o = getPropertyOrPreference(name);
363        if (o != null) {
364            value = Integer.valueOf(o.toString());
365        }
366        return value;
367    }
368    
369    /**
370     * Find the {@link Double} value associated with the given ID by checking
371     * the {@literal "properties"}, and if nothing was found, check the
372     * preferences.
373     *
374     * @param name Property or preference ID. Cannot be {@code null}.
375     * @param dflt Value to return if there is no property or preference
376     * associated with {@code name}
377     *
378     * @return Either the value associated with {@code name} or {@code dflt}.
379     */
380    public double getPropertyOrPreference(String name, double dflt) {
381        double value = dflt;
382        Object o = getPropertyOrPreference(name);
383        if (o != null) {
384            value = Double.valueOf(o.toString());
385        }
386        return value;
387    }
388    
389    /**
390     * Find the {@link Boolean} value associated with the given ID by checking
391     * the {@literal "properties"}, and if nothing was found, check the
392     * preferences.
393     *
394     * @param name Property or preference ID. Cannot be {@code null}.
395     * @param dflt Value to return if there is no property or preference
396     * associated with {@code name}
397     *
398     * @return Either the value associated with {@code name} or {@code dflt}.
399     */
400    public boolean getPropertyOrPreference(String name, boolean dflt) {
401        boolean value = dflt;
402        Object o = getPropertyOrPreference(name);
403        if (o != null) {
404            value = Boolean.valueOf(o.toString());
405        }
406        return value;
407    }
408    
409    /**
410     * Returns information about the current version of McIDAS-V and the IDV,
411     * along with their respective build dates.
412     * 
413     * @return {@code Hashtable} containing versioning information.
414     */
415    public Hashtable<String, String> getVersionInfo() {
416        String versionFile = (String)getProperty(Constants.PROP_VERSIONFILE);
417        Properties props =
418            Misc.readProperties(versionFile, null, getClass());
419            
420        String mcvBuild = props.getProperty(PROP_BUILD_DATE, "Unknown");
421        
422        Hashtable<String, String> table = new Hashtable<>();
423        table.put("mcv.version.general", getMcIdasVersion());
424        table.put("mcv.version.build", mcvBuild);
425        table.put("idv.version.general", getVersion());
426        table.put("idv.version.build", getBuildDate());
427        table.put("visad.version.general", getVisadVersion());
428        table.put("visad.version.build", getVisadDate());
429        table.put("netcdf.version.general", getNetcdfVersion());
430        table.put("netcdf.version.build", getNetcdfDate());
431        return table;
432    }
433    
434    /**
435     * Return the timestamp from when {@code ncIdv.jar} was created.
436     *
437     * @return {@code String} representation of the creation timestamp.
438     */
439    public String getNetcdfDate() {
440        if (netcdfDate == null) {
441            Map<String, String> props = SystemState.queryNcidvBuildProperties();
442            netcdfDate = props.get("buildDate");
443            netcdfVersion = props.get("version");
444        }
445        return netcdfDate;
446    }
447    
448    /**
449     * Return the version information within {@code ncIdv.jar}.
450     *
451     * @return Version of {@code ncIdv.jar} shipped by McIDAS-V.
452     */
453    public String getNetcdfVersion() {
454        if (netcdfVersion == null) {
455            Map<String, String> props = SystemState.queryNcidvBuildProperties();
456            netcdfDate = props.get("buildDate");
457            netcdfVersion = props.get("version");
458        }
459        return netcdfVersion;
460    }
461    
462    /**
463     * Return the timestamp from when visad.jar was created.
464     * 
465     * @return {@code String} representation of the creation timestamp.
466     * Likely to change formatting over time.
467     */
468    public String getVisadDate() {
469        if (visadDate == null) {
470            Map<String, String> props = SystemState.queryVisadBuildProperties();
471            visadDate = props.get(Constants.PROP_VISAD_DATE);
472            visadVersion = props.get(Constants.PROP_VISAD_REVISION);
473        }
474        return visadDate;
475    }
476    
477    /**
478     * Return the {@literal "version"} of VisAD.
479     * 
480     * @return Currently returns whatever the SVN revision number was when
481     * visad.jar was built. 
482     */
483    public String getVisadVersion() {
484        if (visadVersion == null) {
485            Map<String, String> props = SystemState.queryVisadBuildProperties();
486            visadDate = props.get(Constants.PROP_VISAD_DATE);
487            visadVersion = props.get(Constants.PROP_VISAD_REVISION);
488        }
489        return visadVersion;
490    }
491    
492    public String getIdvVersion() {
493        return getVersion();
494    }
495    
496    /**
497     * Overridden to set default of McIDAS-V
498     */
499    @Override public String getStoreSystemName() {
500        return StartupManager.getInstance().getPlatform().getUserDirectory();
501    }
502    
503    /**
504     * Overridden to get dir of the unnecessary second level directory.
505     */
506    @Override public String getStoreName() {
507        return "";
508    }
509    
510    /**
511     * Connect to McIDAS-V website and look for latest stable version.
512     *
513     * @return Latest stable version.
514     */
515    public String getMcIdasVersionStable() {
516        String offscreen = "0";
517        if (getIdv().getArgsManager().getIsOffScreen()) {
518            offscreen = "1";
519        }
520
521        String version = "";
522        try {
523            version = IOUtil.readContents(Constants.HOMEPAGE_URL+"/"+Constants.VERSION_HANDLER_URL+"?v="+getMcIdasVersion()+"&os="+getOSName()+"&off="+offscreen, "");
524        } catch (Exception e) {
525            logger.warn("Could not get latest McV stable version", e);
526        }
527        return version.trim();
528    }
529    
530    /**
531     * Connect to McIDAS-V website and look for latest pre-release version.
532     *
533     * @return Latest pre-release version.
534     */
535    public String getMcIdasVersionPrerelease() {
536        String version = "";
537        try {
538            String htmlList = IOUtil.readContents(Constants.HOMEPAGE_URL+'/'+Constants.PRERELEASE_URL, "");
539            String lines[] = htmlList.split("\n");
540            for (int i=0; i<lines.length; i++) {
541                String line = lines[i].trim();
542                if (line.matches(".*McIDAS-V_\\d+\\.\\d+.*")) {
543                    line = line.substring(line.indexOf("McIDAS-V_")+9);
544                    String aVersion = line.substring(0, line.indexOf("_"));
545                    if (version.isEmpty()) {
546                        version = aVersion;
547                    } else {
548                        int comp = compareVersions(version, aVersion);
549                        if (comp > 0) {
550                            version = aVersion;
551                        }
552                    }
553                }
554            }
555        } catch (Exception e) {
556            logger.warn("Could not get latest McV pre-release version", e);
557        }
558        return version.trim();
559    }
560    
561    /**
562     * Connect to McIDAS website and look for latest notice.
563     *
564     * @return Contents of notice. String may be empty.
565     */
566    public String getNoticeLatest() {
567        String notice = "";
568        try {
569            notice = IOUtil.readContents(Constants.HOMEPAGE_URL+"/"+Constants.NOTICE_URL+"?requesting="+getMcIdasVersion()+"&os="+getOSName(), "");
570        } catch (Exception e) {
571            logger.warn("Could not get latest notice", e);
572        }
573        if (!notice.contains("<notice>")) {
574            notice = "";
575        }
576        notice = notice.replaceAll("<[/?]notice>","");
577        return notice.trim();
578    }
579    
580    /**
581     * Compare version strings.
582     *
583     * <p>The logic is as follows.
584     * <pre>
585     *    0: thisVersion and thatVersion are equal.
586     *   &lt;0: thisVersion is greater.
587     *   &gt;0: thatVersion is greater.
588     * </pre>
589     *
590     * @param thisVersion First version string to compare.
591     * @param thatVersion Second version string to compare.
592     *
593     * @return Value indicating which of {@code thisVersion} and
594     * {@code thatVersion} is {@literal "greater"}.
595     */
596    public static int compareVersions(String thisVersion, String thatVersion) {
597        int thisInt = versionToInteger(thisVersion);
598        int thatInt = versionToInteger(thatVersion);
599        return thatInt - thisInt;
600    }
601    
602    /**
603     * Turn version strings of the form {@code #.#(a#)}, where # is one or two
604     * digits, a is one of alpha or beta, and () is optional, into an integer
605     * value... (empty) &gt; beta &gt; alpha.
606     *
607     * @param version String representation of version number.
608     *
609     * @return Integer representation of {@code version}.
610     */
611    public static int versionToInteger(String version) {
612        int value = 0;
613        int p;
614        String part;
615        Character one = null;
616        
617        try {
618            // Major version
619            p = version.indexOf('.');
620            if (p > 0) {
621                part = version.substring(0,p);
622                value += Integer.parseInt(part) * 1000000;
623                version = version.substring(p+1);
624            }
625            
626            // Minor version
627            int minor = 0;
628            int i = 0;
629            for (i = 0; i < 2 && i < version.length(); i++) {
630                one = version.charAt(i);
631                if (Character.isDigit(one)) {
632                    if (i > 0) {
633                        minor *= 10;
634                    }
635                    minor += Character.digit(one, 10) * 10000;
636                } else {
637                    break;
638                }
639            }
640            value += minor;
641            if (one != null) {
642                version = version.substring(i);
643            }
644            
645            // Alpha/beta/update/release status
646            if (version.length() == 0) {
647                value += 300;
648            } else if (version.charAt(0) == 'b') {
649                value += 200;
650            } else if (version.charAt(0) == 'a') {
651                value += 100;
652            } else if (version.charAt(0) == 'u') {
653                value += 400;
654            } else if (version.charAt(0) == 'r') {
655                value += 400;
656            }
657            for (i = 0; i < version.length(); i++) {
658                one = version.charAt(i);
659                if (Character.isDigit(one)) {
660                    break;
661                }
662            }
663            if (one != null) {
664                version = version.substring(i);
665            }
666            
667            // Alpha/beta version
668            if (version.length() > 0) {
669                value += Integer.parseInt(version);
670            }
671        } catch (Exception e) {
672            
673        }
674        return value;
675    }
676    
677    public boolean getIsPrerelease() {
678        boolean isPrerelease = false;
679        String version = getMcIdasVersion();
680        if (version.contains("a") || version.contains("b")) {
681            isPrerelease = true;
682        }
683        return isPrerelease;
684    }
685    
686    public void checkForNewerVersion(boolean notifyDialog) {
687        checkForNewerVersionStable(notifyDialog);
688        if (getStore().get(Constants.PREF_PRERELEASE_CHECK, getIsPrerelease())) {
689            checkForNewerVersionPrerelease(notifyDialog);
690        }
691    }
692    
693    public void checkForNewerVersionStable(boolean notifyDialog) {
694        
695        // get the stable version from the website (for statistics recording)
696        String thatVersion = getMcIdasVersionStable();
697        
698        // shortcut the rest of the process if we are processing offscreen
699        if (getIdv().getArgsManager().getIsOffScreen()) {
700            return;
701        }
702        
703        String thisVersion = getMcIdasVersion();
704        String titleText = "Version Check";
705        
706        if (thisVersion.isEmpty() || thatVersion.isEmpty()) {
707            if (notifyDialog) {
708                JOptionPane.showMessageDialog(null,
709                                              "Version check failed",
710                                              titleText,
711                                              JOptionPane.WARNING_MESSAGE);
712            }
713        } else if (compareVersions(thisVersion, thatVersion) > 0) {
714            String labelText = "<html>Version <b>" + thatVersion + "</b> is available<br><br>";
715            labelText += "Visit <a href=\"" + Constants.HOMEPAGE_URL + "\">";
716            labelText += Constants.HOMEPAGE_URL + "</a> to download</html>";
717            
718            JPanel backgroundColorGetterPanel = new JPanel();
719            JEditorPane messageText = new JEditorPane("text/html", labelText);
720            messageText.setBackground(backgroundColorGetterPanel.getBackground());
721            messageText.setEditable(false);
722            messageText.addHyperlinkListener(this);
723            
724            //JLabel message = new JLabel(labelText, JLabel.CENTER);
725            JOptionPane.showMessageDialog(null,
726                                          messageText,
727                                          titleText,
728                                          JOptionPane.INFORMATION_MESSAGE);
729        } else {
730            if (notifyDialog) {
731                String labelText = "<html>This version (<b>" + thisVersion + "</b>) is up to date</html>";
732                JLabel message = new JLabel(labelText, JLabel.CENTER);
733                JOptionPane.showMessageDialog(null,
734                                              message,
735                                              titleText,
736                                              JOptionPane.INFORMATION_MESSAGE);
737            }
738        }
739    }
740    
741    public void checkForNewerVersionPrerelease(boolean notifyDialog) {
742        
743        // shortcut the rest of the process if we are processing offscreen
744        if (getIdv().getArgsManager().getIsOffScreen()) {
745            return;
746        }
747        
748        String thisVersion = getMcIdasVersion();
749        String thatVersion = getMcIdasVersionPrerelease();
750        String titleText = "Prerelease Check";
751        
752        if (thisVersion.isEmpty() || thatVersion.isEmpty()) {
753            if (notifyDialog) {
754                JOptionPane.showMessageDialog(
755                    null, "No prerelease version available", titleText,
756                    JOptionPane.WARNING_MESSAGE);
757            }
758        } else if (compareVersions(thisVersion, thatVersion) > 0) {
759            String labelText = "<html>Prerelease <b>" + thatVersion + "</b> is available<br><br>";
760            labelText += "Visit <a href=\"" + Constants.HOMEPAGE_URL+'/'+Constants.PRERELEASE_URL + "\">";
761            labelText += Constants.HOMEPAGE_URL+'/'+Constants.PRERELEASE_URL + "</a> to download</html>";
762            
763            JPanel backgroundColorGetterPanel = new JPanel();
764            JEditorPane messageText = new JEditorPane("text/html", labelText);
765            messageText.setBackground(backgroundColorGetterPanel.getBackground());
766            messageText.setEditable(false);
767            messageText.addHyperlinkListener(this);
768            
769            // JLabel message = new JLabel(labelText, JLabel.CENTER);
770            JOptionPane.showMessageDialog(null,
771                                          messageText,
772                                          titleText,
773                                          JOptionPane.INFORMATION_MESSAGE);
774        } else {
775            if (notifyDialog) {
776                String labelText = "<html>This version (<b>" + thisVersion + "</b>) is up to date</html>";
777                JLabel message = new JLabel(labelText, JLabel.CENTER);
778                JOptionPane.showMessageDialog(null,
779                                              message,
780                                              titleText,
781                                              JOptionPane.INFORMATION_MESSAGE);
782            }
783        }
784    }
785    
786    public void checkForNotice(boolean notifyDialog) {
787        
788        // Shortcut this whole process if we are processing offscreen
789        if (getIdv().getArgsManager().getIsOffScreen()) {
790            return;
791        }
792        
793        String thisNotice = getNoticeCached().trim();
794        String thatNotice = getNoticeLatest().trim();
795        String titleText = "New Notice";
796        String labelText = thatNotice;
797        
798        if (thatNotice.isEmpty()) {
799            setNoticeCached(thatNotice);
800            if (notifyDialog) {
801                titleText = "No Notice";
802                JLabel message = new JLabel("There is no current notice", JLabel.CENTER);
803                JOptionPane.showMessageDialog(null, message, titleText,
804                    JOptionPane.INFORMATION_MESSAGE);
805            }
806        } else if (!thisNotice.equals(thatNotice)) {
807            setNoticeCached(thatNotice);
808            
809            JPanel backgroundColorGetterPanel = new JPanel();
810            JEditorPane messageText = new JEditorPane("text/html", labelText);
811            messageText.setBackground(backgroundColorGetterPanel.getBackground());
812            messageText.setEditable(false);
813            messageText.addHyperlinkListener(this);
814            JOptionPane.showMessageDialog(null, messageText, titleText,
815                JOptionPane.INFORMATION_MESSAGE);
816        } else {
817            if (notifyDialog) {
818                titleText = "Previous Notice";
819                JPanel bgPanel = new JPanel();
820                JEditorPane messageText =
821                    new JEditorPane("text/html", labelText);
822                messageText.setBackground(bgPanel.getBackground());
823                messageText.setEditable(false);
824                messageText.addHyperlinkListener(this);
825                JOptionPane.showMessageDialog(null, messageText, titleText,
826                    JOptionPane.INFORMATION_MESSAGE);
827            }
828        }
829    }
830    
831    /**
832     * Debug a McIDAS-V {@literal "system notice"} before sending it to all
833     * users!
834     *
835     * @param noticeContents Contents of the notice.
836     * @param notifyDialog if {@code true}, show notice even if already seen.
837     * @param disableCache Whether or not {@code noticeContents} will be cached.
838     */
839    public void debugNotice(String noticeContents, boolean notifyDialog,
840                            boolean disableCache)
841    {
842        // Shortcut this whole process if we are processing offscreen
843        if (getIdv().getArgsManager().getIsOffScreen()) {
844            return;
845        }
846        
847        String thisNotice;
848        thisNotice = disableCache ? "" : getNoticeCached().trim();
849        String thatNotice = noticeContents.trim();
850        String labelText = thatNotice;
851        
852        if (thatNotice.isEmpty()) {
853            if (!disableCache) {
854                setNoticeCached(thatNotice);
855            }
856            if (notifyDialog) {
857                String titleText = "No Notice";
858                JLabel message =
859                    new JLabel("There is no current notice", JLabel.CENTER);
860                JOptionPane.showMessageDialog(null, message, titleText,
861                    JOptionPane.INFORMATION_MESSAGE);
862            }
863        } else if (!thisNotice.equals(thatNotice)) {
864            if (!disableCache) {
865                setNoticeCached(thatNotice);
866            }
867            String titleText = "New Notice";
868            JPanel backgroundColorGetterPanel = new JPanel();
869            JEditorPane messageText = new JEditorPane("text/html", labelText);
870            messageText.setBackground(backgroundColorGetterPanel.getBackground());
871            messageText.setEditable(false);
872            messageText.addHyperlinkListener(this);
873            JOptionPane.showMessageDialog(null, messageText, titleText,
874                JOptionPane.INFORMATION_MESSAGE);
875        } else {
876            if (notifyDialog) {
877                String titleText = "Previous Notice";
878                JPanel bgPanel = new JPanel();
879                JEditorPane messageText =
880                    new JEditorPane("text/html", labelText);
881                messageText.setBackground(bgPanel.getBackground());
882                messageText.setEditable(false);
883                messageText.addHyperlinkListener(this);
884                JOptionPane.showMessageDialog(null, messageText, titleText,
885                    JOptionPane.INFORMATION_MESSAGE);
886            }
887        }
888    }
889    
890    private String getNoticePath() {
891        return StartupManager.getInstance().getPlatform().getUserFile("notice.txt");
892    }
893    
894    private String getNoticeCached() {
895        StringBuilder notice = new StringBuilder(1024);
896        try{
897            FileReader fstream = new FileReader(getNoticePath());
898            BufferedReader in = new BufferedReader(fstream);
899            String line;
900            while ((line = in.readLine()) != null) {
901                notice.append(line).append('\n');
902            }
903            in.close();
904        } catch (Exception e) {
905            logger.warn("Could not get cached notice", e);
906        }
907        return notice.toString();
908    }
909    
910    private void setNoticeCached(String notice) {
911        try {
912            FileWriter fstream = new FileWriter(getNoticePath());
913            BufferedWriter out = new BufferedWriter(fstream);
914            out.write(notice);
915            out.close();
916        } catch (Exception e) {
917            logger.warn("Could not cache downloaded notice", e);
918        }
919    }
920}