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