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