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