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