JAL-3583 better handling of null or empty tooltip on annotation
[jalview.git] / src / jalview / util / Platform.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.util;
22
23 import java.awt.Component;
24 import java.awt.Dimension;
25 import java.awt.Toolkit;
26 import java.awt.event.KeyEvent;
27 import java.awt.event.MouseEvent;
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileOutputStream;
31 import java.io.FileReader;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.Reader;
36 import java.lang.reflect.Method;
37 import java.net.URL;
38 import java.nio.channels.Channels;
39 import java.nio.channels.ReadableByteChannel;
40 import java.nio.file.Files;
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.nio.file.StandardCopyOption;
44 import java.nio.file.attribute.BasicFileAttributes;
45 import java.util.Date;
46 import java.util.Locale;
47 import java.util.Map;
48 import java.util.Properties;
49 import java.util.logging.ConsoleHandler;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52
53 import javax.swing.SwingUtilities;
54
55 import org.json.simple.parser.JSONParser;
56 import org.json.simple.parser.ParseException;
57
58 import com.stevesoft.pat.Regex;
59
60 import jalview.bin.Jalview;
61 import jalview.javascript.json.JSON;
62 import swingjs.api.JSUtilI;
63
64 /**
65  * System platform information used by Applet and Application
66  * 
67  * @author Jim Procter
68  */
69 public class Platform
70 {
71
72   private static boolean isJS = /** @j2sNative true || */
73           false;
74
75   private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
76           isWin = null;
77
78   private static Boolean isHeadless = null;
79
80   private static swingjs.api.JSUtilI jsutil;
81
82   static
83   {
84     if (isJS)
85     {
86       try
87       {
88         // this is ok - it's a highly embedded method in Java; the deprecation
89         // is
90         // really a recommended best practice.
91         jsutil = ((JSUtilI) Class.forName("swingjs.JSUtil").newInstance());
92       } catch (InstantiationException | IllegalAccessException
93               | ClassNotFoundException e)
94       {
95         e.printStackTrace();
96       }
97     }
98   }
99   // private static Boolean isHeadless = null;
100
101   /**
102    * added to group mouse events into Windows and nonWindows (mac, unix, linux)
103    * 
104    * @return
105    */
106   public static boolean isMac()
107   {
108     return (isMac == null
109             ? (isMac = (System.getProperty("os.name").indexOf("Mac") >= 0))
110             : isMac);
111   }
112
113   public static int SHORTCUT_KEY_MASK = (Platform.isMac()
114           ? KeyEvent.META_DOWN_MASK
115           : KeyEvent.CTRL_DOWN_MASK);
116
117   static
118   {
119     // Using non-deprecated Extended key mask modifiers, but Java 8 has no
120     // getMenuShortcutKeyMaskEx method
121     Toolkit tk = Toolkit.getDefaultToolkit();
122     Method method = null;
123     try
124     {
125       method = tk.getClass().getMethod("getMenuShortcutKeyMaskEx");
126     } catch (NoSuchMethodException e)
127     {
128       System.err.println(
129               "Could not find Toolkit method getMenuShortcutKeyMaskEx. Trying getMenuShortcutKeyMask.");
130     }
131     if (method == null)
132     {
133       try
134       {
135         method = tk.getClass().getMethod("getMenuShortcutKeyMask");
136       } catch (NoSuchMethodException e)
137       {
138         System.err.println(
139                 "Could not find Toolkit method getMenuShortcutKeyMaskEx or getMenuShortcutKeyMask.");
140         e.printStackTrace();
141       }
142     }
143     if (method != null)
144     {
145       try
146       {
147         method.setAccessible(true);
148         SHORTCUT_KEY_MASK = ((int) method.invoke(tk, new Object[0]));
149       } catch (Exception e)
150       {
151         e.printStackTrace();
152       }
153     }
154     if (SHORTCUT_KEY_MASK <= 0xF)
155     {
156       // shift this into the extended region (was Java 8)
157       SHORTCUT_KEY_MASK = SHORTCUT_KEY_MASK << 6;
158     }
159   }
160
161   /**
162    * added to group mouse events into Windows and nonWindows (mac, unix, linux)
163    * 
164    * @return
165    */
166   public static boolean isWin()
167   {
168     return (isWin == null
169             ? (isWin = (System.getProperty("os.name").indexOf("Win") >= 0))
170             : isWin);
171   }
172
173   /**
174    * 
175    * @return true if HTML5 JavaScript
176    */
177   public static boolean isJS()
178   {
179     return isJS;
180   }
181
182   /**
183    * sorry folks - Macs really are different
184    * 
185    * BH: disabled for SwingJS -- will need to check key-press issues
186    * 
187    * @return true if we do things in a special way.
188    */
189   public static boolean isAMacAndNotJS()
190   {
191     return (isNoJSMac == null ? (isNoJSMac = !isJS && isMac()) : isNoJSMac);
192   }
193
194   /**
195    * Check if we are on a Microsoft plaform...
196    * 
197    * @return true if we have to cope with another platform variation
198    */
199   public static boolean isWindowsAndNotJS()
200   {
201     return (isNoJSWin == null ? (isNoJSWin = !isJS && isWin()) : isNoJSWin);
202   }
203
204   /**
205    *
206    * @return true if we are running in non-interactive no UI mode
207    */
208   public static boolean isHeadless()
209   {
210     if (isHeadless == null)
211     {
212       isHeadless = "true".equals(System.getProperty("java.awt.headless"));
213     }
214     return isHeadless;
215   }
216
217   /**
218    * 
219    * @return nominal maximum command line length for this platform
220    */
221   public static int getMaxCommandLineLength()
222   {
223     // TODO: determine nominal limits for most platforms.
224     return 2046; // this is the max length for a windows NT system.
225   }
226
227   /**
228    * Answers the input with every backslash replaced with a double backslash (an
229    * 'escaped' single backslash)
230    * 
231    * @param s
232    * @return
233    */
234   public static String escapeBackslashes(String s)
235   {
236     return s == null ? null : s.replace("\\", "\\\\");
237   }
238
239   /**
240    * Answers true if the mouse event has Meta-down (Command key on Mac) or
241    * Ctrl-down (on other o/s). Note this answers _false_ if the Ctrl key is
242    * pressed instead of the Meta/Cmd key on Mac. To test for Ctrl-pressed on
243    * Mac, you can use e.isPopupTrigger().
244    * 
245    * @param e
246    * @return
247    */
248   public static boolean isControlDown(MouseEvent e)
249   {
250     return isControlDown(e, isMac());
251   }
252
253   /**
254    * Overloaded version of method (to allow unit testing)
255    * 
256    * @param e
257    * @param aMac
258    * @return
259    */
260   protected static boolean isControlDown(MouseEvent e, boolean aMac)
261   {
262     //
263     // System.out.println(e.isPopupTrigger()
264     // + " " + ((SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0)
265     // + " " + e.isControlDown());
266     return (aMac
267             ? !e.isPopupTrigger()
268                     && (SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0
269             : e.isControlDown());
270   }
271
272   // BH: I don't know about that previous method. Here is what SwingJS uses.
273   // Notice the distinction in mouse events. (BUTTON3_MASK == META)
274   //
275   // private static boolean isPopupTrigger(int id, int mods, boolean isWin) {
276   // boolean rt = ((mods & InputEvent.BUTTON3_MASK) != 0);
277   // if (isWin) {
278   // if (id != MouseEvent.MOUSE_RELEASED)
279   // return false;
280   ////
281   //// // Oddly, Windows returns InputEvent.META_DOWN_MASK on release, though
282   //// // BUTTON3_DOWN_MASK for pressed. So here we just accept both.
283   ////
284   //// actually, we can use XXX_MASK, not XXX_DOWN_MASK and avoid this issue,
285   // because
286   //// J2S adds the appropriate extended (0x3FC0) and simple (0x3F) modifiers.
287   ////
288   // return rt;
289   // } else {
290   // // mac, linux, unix
291   // if (id != MouseEvent.MOUSE_PRESSED)
292   // return false;
293   // boolean lt = ((mods & InputEvent.BUTTON1_MASK) != 0);
294   // boolean ctrl = ((mods & InputEvent.CTRL_MASK) != 0);
295   // return rt || (ctrl && lt);
296   // }
297   // }
298   //
299
300   /**
301    * Windows (not Mac, Linux, or Unix) and right button to test for the
302    * right-mouse pressed event in Windows that would have opened a menu or a
303    * Mac.
304    * 
305    * @param e
306    * @return
307    */
308   public static boolean isWinRightButton(MouseEvent e)
309   {
310     // was !isAMac(), but that is true also for Linux and Unix and JS,
311
312     return isWin() && SwingUtilities.isRightMouseButton(e);
313   }
314
315   /**
316    * Windows (not Mac, Linux, or Unix) and middle button -- for mouse wheeling
317    * without pressing the button.
318    * 
319    * @param e
320    * @return
321    */
322   public static boolean isWinMiddleButton(MouseEvent e)
323   {
324     // was !isAMac(), but that is true also for Linux and Unix and JS
325     return isWin() && SwingUtilities.isMiddleMouseButton(e);
326   }
327
328   public static boolean allowMnemonics()
329   {
330     return !isMac();
331   }
332
333   public final static int TIME_RESET = 0;
334
335   public final static int TIME_MARK = 1;
336
337   public static final int TIME_SET = 2;
338
339   public static final int TIME_GET = 3;
340
341   public static long time, mark, set, duration;
342
343   /**
344    * typical usage:
345    * 
346    * Platform.timeCheck(null, Platform.TIME_MARK);
347    * 
348    * ...
349    * 
350    * Platform.timeCheck("some message", Platform.TIME_MARK);
351    * 
352    * reset...[set/mark]n...get
353    * 
354    * @param msg
355    * @param mode
356    */
357   public static void timeCheck(String msg, int mode)
358   {
359     long t = System.currentTimeMillis();
360     switch (mode)
361     {
362     case TIME_RESET:
363       time = mark = t;
364       duration = 0;
365       if (msg != null)
366       {
367         System.err.println("Platform: timer reset\t\t\t" + msg);
368       }
369       break;
370     case TIME_MARK:
371       if (set > 0)
372       {
373         // total time between set/mark points
374         duration += (t - set);
375       }
376       else
377       {
378         if (time == 0)
379         {
380           time = mark = t;
381         }
382         if (msg != null)
383         {
384           System.err.println("Platform: timer mark\t" + ((t - time) / 1000f)
385                   + "\t" + ((t - mark) / 1000f) + "\t" + msg);
386         }
387         mark = t;
388       }
389       break;
390     case TIME_SET:
391       set = t;
392       break;
393     case TIME_GET:
394       if (msg != null)
395       {
396         System.err.println("Platform: timer dur\t" + ((t - time) / 1000f)
397                 + "\t" + ((duration) / 1000f) + "\t" + msg);
398       }
399       set = 0;
400       break;
401     }
402   }
403
404   public static void cacheFileData(String path, Object data)
405   {
406     if (isJS && data != null)
407     {
408       jsutil.cachePathData(path, data);
409     }
410   }
411
412   public static void cacheFileData(File file)
413   {
414     if (isJS)
415     {
416       byte[] data = Platform.getFileBytes(file);
417       {
418         if (data != null)
419         {
420           cacheFileData(file.toString(), data);
421         }
422       }
423     }
424   }
425
426   public static byte[] getFileBytes(File f)
427   {
428     return (isJS && f != null ? jsutil.getBytes(f) : null);
429   }
430
431   public static byte[] getFileAsBytes(String fileStr)
432   {
433     if (isJS && fileStr != null)
434     {
435       byte[] bytes = (byte[]) jsutil.getFile(fileStr, false);
436       cacheFileData(fileStr, bytes);
437       return bytes;
438     }
439     return null;
440   }
441
442   public static String getFileAsString(String url)
443   {
444     if (isJS && url != null)
445     {
446       String ret = (String) jsutil.getFile(url, true);
447       cacheFileData(url, ret);
448       return ret;
449     }
450     return null;
451   }
452
453   public static boolean setFileBytes(File f, String urlstring)
454   {
455     if (isJS && f != null && urlstring != null)
456     {
457       @SuppressWarnings("unused")
458       byte[] bytes = getFileAsBytes(urlstring);
459       jsutil.setFileBytes(f, bytes);
460       return true;
461     }
462     return false;
463   }
464
465   public static void addJ2SBinaryType(String ext)
466   {
467     if (isJS)
468     {
469       jsutil.addBinaryFileType(ext);
470     }
471   }
472
473   /**
474    * Encode the URI using JavaScript encodeURIComponent
475    * 
476    * @param value
477    * @return encoded value
478    */
479   public static String encodeURI(String value)
480   {
481     /**
482      * @j2sNative value = encodeURIComponent(value);
483      */
484     return value;
485   }
486
487   /**
488    * Open the URL using a simple window call if this is JavaScript
489    * 
490    * @param url
491    * @return true if window has been opened
492    */
493   public static boolean openURL(String url) throws IOException
494   {
495     if (!isJS())
496     {
497       return false;
498     }
499     /**
500      * @j2sNative
501      * 
502      * 
503      *            window.open(url);
504      */
505     return true;
506   }
507
508   public static String getUniqueAppletID()
509   {
510     return (isJS ? (String) jsutil.getAppletAttribute("_uniqueId") : null);
511   }
512
513   /**
514    * Read the Info block for this applet.
515    * 
516    * @param prefix
517    *          "jalview_"
518    * @param p
519    * @return unique id for this applet
520    */
521   public static void readInfoProperties(String prefix, Properties p)
522   {
523     if (isJS)
524     {
525       String id = getUniqueAppletID();
526
527       String key = "";
528       String value = "";
529       @SuppressWarnings("unused")
530       Object info = jsutil.getAppletAttribute("__Info");
531       /**
532        * @j2sNative for (key in info) { value = info[key];
533        */
534
535       if (key.indexOf(prefix) == 0)
536       {
537         System.out.println("Platform id=" + id + " reading Info." + key
538                 + " = " + value);
539         p.put(key, value);
540
541       }
542
543       /**
544        * @j2sNative }
545        */
546     }
547   }
548
549   public static void setAjaxJSON(URL url)
550   {
551     if (isJS())
552     {
553       JSON.setAjax(url);
554     }
555   }
556
557   public static Object parseJSON(InputStream response)
558           throws IOException, ParseException
559   {
560     if (isJS())
561     {
562       return JSON.parse(response);
563     }
564
565     BufferedReader br = null;
566     try
567     {
568       br = new BufferedReader(new InputStreamReader(response, "UTF-8"));
569       return new JSONParser().parse(br);
570     } finally
571     {
572       if (br != null)
573       {
574         try
575         {
576           br.close();
577         } catch (IOException e)
578         {
579           // ignore
580         }
581       }
582     }
583   }
584
585   public static Object parseJSON(String json) throws ParseException
586   {
587     return (isJS() ? JSON.parse(json) : new JSONParser().parse(json));
588   }
589
590   public static Object parseJSON(Reader r)
591           throws IOException, ParseException
592   {
593     if (r == null)
594     {
595       return null;
596     }
597
598     if (!isJS())
599     {
600       return new JSONParser().parse(r);
601     }
602     // Using a file reader is not currently supported in SwingJS JavaScript
603
604     if (r instanceof FileReader)
605     {
606       throw new IOException(
607               "StringJS does not support FileReader parsing for JSON -- but it could...");
608     }
609     return JSON.parse(r);
610   }
611
612   /**
613    * Dump the input stream to an output file.
614    * 
615    * @param is
616    * @param outFile
617    * @throws IOException
618    *           if the file cannot be created or there is a problem reading the
619    *           input stream.
620    */
621   public static void streamToFile(InputStream is, File outFile)
622           throws IOException
623   {
624
625     if (isJS)
626     {
627       jsutil.setFileBytes(outFile, is);
628       return;
629     }
630
631     FileOutputStream fio = new FileOutputStream(outFile);
632     try
633     {
634       byte[] bb = new byte[32 * 1024];
635       int l;
636       while ((l = is.read(bb)) > 0)
637       {
638         fio.write(bb, 0, l);
639       }
640     } finally
641     {
642       fio.close();
643     }
644   }
645
646   /**
647    * Add a known domain that implements access-control-allow-origin:*
648    * 
649    * These should be reviewed periodically.
650    * 
651    * @param domain
652    *          for a service that is not allowing ajax
653    * 
654    * @author hansonr@stolaf.edu
655    * 
656    */
657   public static void addJ2SDirectDatabaseCall(String domain)
658   {
659
660     if (isJS)
661     {
662       jsutil.addDirectDatabaseCall(domain);
663
664       System.out.println(
665               "Platform adding known access-control-allow-origin * for domain "
666                       + domain);
667     }
668
669   }
670
671   /**
672    * Allow for URL-line command arguments. Untested.
673    * 
674    */
675   public static void getURLCommandArguments()
676   {
677
678     try
679     {
680       /**
681        * Retrieve the first query field as command arguments to Jalview. Include
682        * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's
683        * __Info.args element to this value.
684        * 
685        * @j2sNative var a =
686        *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
687        *            + "?").split("?")[1].split("#")[0]); a &&
688        *            (J2S.thisApplet.__Info.args = a.split(" "));
689        * 
690        *            System.out.println("URL arguments: " + a);
691        */
692     } catch (Throwable t)
693     {
694     }
695   }
696
697   /**
698    * A (case sensitive) file path comparator that ignores the difference between
699    * / and \
700    * 
701    * @param path1
702    * @param path2
703    * @return
704    */
705   public static boolean pathEquals(String path1, String path2)
706   {
707     if (path1 == null)
708     {
709       return path2 == null;
710     }
711     if (path2 == null)
712     {
713       return false;
714     }
715     String p1 = path1.replace('\\', '/');
716     String p2 = path2.replace('\\', '/');
717     return p1.equals(p2);
718   }
719
720   ///////////// JAL-3253 Applet additions //////////////
721
722   /**
723    * Retrieve the object's embedded size from a div's style on a page if
724    * embedded in SwingJS.
725    * 
726    * @param frame
727    *          JFrame or JInternalFrame
728    * @param defaultWidth
729    *          use -1 to return null (no default size)
730    * @param defaultHeight
731    * @return the embedded dimensions or null (no default size or not embedded)
732    */
733   public static Dimension getDimIfEmbedded(Component frame,
734           int defaultWidth, int defaultHeight)
735   {
736     Dimension d = null;
737     if (isJS)
738     {
739       d = (Dimension) getEmbeddedAttribute(frame, "dim");
740     }
741     return (d == null && defaultWidth >= 0
742             ? new Dimension(defaultWidth, defaultHeight)
743             : d);
744
745   }
746
747   public static Regex newRegex(String regex)
748   {
749     return newRegex(regex, null);
750   }
751
752   public static Regex newRegex(String searchString, String replaceString)
753   {
754     ensureRegex();
755     return (replaceString == null ? new Regex(searchString)
756             : new Regex(searchString, replaceString));
757   }
758
759   public static Regex newRegexPerl(String code)
760   {
761     ensureRegex();
762     return Regex.perlCode(code);
763   }
764
765   /**
766    * Initialize Java debug logging. A representative sample -- adapt as desired.
767    */
768   public static void startJavaLogging()
769   {
770     /**
771      * @j2sIgnore
772      */
773     {
774       logClass("java.awt.EventDispatchThread", "java.awt.EventQueue",
775               "java.awt.Component", "java.awt.focus.Component",
776               "java.awt.event.Component",
777               "java.awt.focus.DefaultKeyboardFocusManager");
778     }
779   }
780
781   /**
782    * Initiate Java logging for a given class. Only for Java, not JavaScript;
783    * Allows debugging of complex event processing.
784    * 
785    * @param className
786    */
787   public static void logClass(String... classNames)
788   {
789     /**
790      * @j2sIgnore
791      * 
792      * 
793      */
794     {
795       Logger rootLogger = Logger.getLogger("");
796       rootLogger.setLevel(Level.ALL);
797       ConsoleHandler consoleHandler = new ConsoleHandler();
798       consoleHandler.setLevel(Level.ALL);
799       for (int i = classNames.length; --i >= 0;)
800       {
801         Logger logger = Logger.getLogger(classNames[i]);
802         logger.setLevel(Level.ALL);
803         logger.addHandler(consoleHandler);
804       }
805     }
806   }
807
808   /**
809    * load a resource -- probably a core file -- if and only if a particular
810    * class has not been instantialized. We use a String here because if we used
811    * a .class object, that reference itself would simply load the class, and we
812    * want the core package to include that as well.
813    * 
814    * @param resourcePath
815    * @param className
816    */
817   public static void loadStaticResource(String resourcePath,
818           String className)
819   {
820     if (isJS)
821     {
822       jsutil.loadResourceIfClassUnknown(resourcePath, className);
823     }
824   }
825
826   public static void ensureRegex()
827   {
828     if (isJS)
829     {
830       loadStaticResource("core/core_stevesoft.z.js",
831               "com.stevesoft.pat.Regex");
832     }
833   }
834
835   /**
836    * Set the "app" property of the HTML5 applet object, for example,
837    * "testApplet.app", to point to the Jalview instance. This will be the object
838    * that page developers use that is similar to the original Java applet object
839    * that was accessed via LiveConnect.
840    * 
841    * @param j
842    */
843   public static void setAppClass(Object j)
844   {
845     if (isJS)
846     {
847       jsutil.setAppClass(j);
848     }
849   }
850
851   /**
852    *
853    * If this frame is embedded in a web page, return a known type.
854    * 
855    * @param frame
856    *          a JFrame or JInternalFrame
857    * @param type
858    *          "name", "node", "init", "dim", or any DOM attribute, such as "id"
859    * @return null if frame is not embedded.
860    */
861   public static Object getEmbeddedAttribute(Component frame, String type)
862   {
863     return (isJS ? jsutil.getEmbeddedAttribute(frame, type) : null);
864   }
865
866   public static void stackTrace()
867   {
868     try
869     {
870       throw new NullPointerException();
871     } catch (Exception e)
872     {
873       e.printStackTrace();
874     }
875
876   }
877
878   public static URL getDocumentBase()
879   {
880     return (isJS ? jsutil.getDocumentBase() : null);
881   }
882
883   public static URL getCodeBase()
884   {
885     return (isJS ? jsutil.getCodeBase() : null);
886   }
887
888   public static String getUserPath(String subpath)
889   {
890     char sep = File.separatorChar;
891     return System.getProperty("user.home") + sep
892             + subpath.replace('/', sep);
893   }
894
895   /**
896    * This method enables checking if a cached file has exceeded a certain
897    * threshold(in days)
898    * 
899    * @param file
900    *          the cached file
901    * @param noOfDays
902    *          the threshold in days
903    * @return
904    */
905   public static boolean isFileOlderThanThreshold(File file, int noOfDays)
906   {
907     if (isJS())
908     {
909       // not meaningful in SwingJS -- this is a session-specific temp file. It
910       // doesn't have a timestamp.
911       return false;
912     }
913     Path filePath = file.toPath();
914     BasicFileAttributes attr;
915     int diffInDays = 0;
916     try
917     {
918       attr = Files.readAttributes(filePath, BasicFileAttributes.class);
919       diffInDays = (int) ((new Date().getTime()
920               - attr.lastModifiedTime().toMillis())
921               / (1000 * 60 * 60 * 24));
922       // System.out.println("Diff in days : " + diffInDays);
923     } catch (IOException e)
924     {
925       e.printStackTrace();
926     }
927     return noOfDays <= diffInDays;
928   }
929
930   /**
931    * Get the leading integer part of a string that begins with an integer.
932    * 
933    * @param input
934    *          - the string input to process
935    * @param failValue
936    *          - value returned if unsuccessful
937    * @return
938    */
939   public static int getLeadingIntegerValue(String input, int failValue)
940   {
941     if (input == null)
942     {
943       return failValue;
944     }
945     if (isJS)
946     {
947       int val = /** @j2sNative 1 ? parseInt(input) : */
948               0;
949       return (val == val + 0 ? val : failValue);
950     }
951     // JavaScript does not support Regex ? lookahead
952     String[] parts = input.split("(?=\\D)(?<=\\d)");
953     if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
954     {
955       return Integer.valueOf(parts[0]);
956     }
957     return failValue;
958   }
959
960   public static Map<String, Object> getAppletInfoAsMap()
961   {
962     return (isJS ? jsutil.getAppletInfoAsMap() : null);
963   }
964
965   /**
966    * Get the SwingJS applet ID and combine that with the frameType
967    * 
968    * @param frameType
969    *          "alignment", "desktop", etc., or null
970    * @return
971    */
972   public static String getAppID(String frameType)
973   {
974
975     String id = Jalview.getInstance().j2sAppletID;
976     if (id == null)
977     {
978       Jalview.getInstance().j2sAppletID = id = (isJS
979               ? (String) jsutil.getAppletAttribute("_id")
980               : "jalview");
981     }
982     return id + (frameType == null ? "" : "-" + frameType);
983   }
984
985   /**
986    * Option to avoid unnecessary seeking of nonexistent resources in JavaScript.
987    * Works in Java as well.
988    * 
989    * @param loc
990    * @return
991    */
992   public static Locale getLocaleOrNone(Locale loc)
993   {
994     return (isJS && loc.getLanguage() == "en" ? new Locale("") : loc);
995   }
996
997   /**
998    * From UrlDownloadClient; trivial in JavaScript; painful in Java.
999    * 
1000    * @param urlstring
1001    * @param outfile
1002    * @throws IOException
1003    */
1004   public static void download(String urlstring, String outfile)
1005           throws IOException
1006   {
1007     Path temp = null;
1008     try (InputStream is = new URL(urlstring).openStream())
1009     {
1010       if (isJS)
1011       { // so much easier!
1012         streamToFile(is, new File(outfile));
1013         return;
1014       }
1015       temp = Files.createTempFile(".jalview_", ".tmp");
1016       try (FileOutputStream fos = new FileOutputStream(temp.toString());
1017               ReadableByteChannel rbc = Channels.newChannel(is))
1018       {
1019         fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
1020         // copy tempfile to outfile once our download completes
1021         // incase something goes wrong
1022         Files.copy(temp, Paths.get(outfile),
1023                 StandardCopyOption.REPLACE_EXISTING);
1024       }
1025     } catch (IOException e)
1026     {
1027       throw e;
1028     } finally
1029     {
1030       try
1031       {
1032         if (temp != null)
1033         {
1034           Files.deleteIfExists(temp);
1035         }
1036       } catch (IOException e)
1037       {
1038         System.out.println("Exception while deleting download temp file: "
1039                 + e.getMessage());
1040       }
1041     }
1042   }
1043
1044 }