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