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