JAL-4154 Add Platform.forArch method.
[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.Objects;
51 import java.util.Properties;
52 import java.util.logging.ConsoleHandler;
53 import java.util.logging.Level;
54 import java.util.logging.Logger;
55
56 import javax.swing.SwingUtilities;
57
58 import org.json.simple.parser.JSONParser;
59 import org.json.simple.parser.ParseException;
60
61 import com.stevesoft.pat.Regex;
62
63 import jalview.bin.Jalview;
64 import jalview.javascript.json.JSON;
65 import swingjs.api.JSUtilI;
66 /**
67  * System platform information used by Applet and Application
68  * 
69  * @author Jim Procter
70  */
71 public class Platform
72 {
73
74   private static boolean isJS = /** @j2sNative true || */
75           false;
76
77   private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
78           isWin = null, isLinux = null;
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    * 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 (!GraphicsEnvironment.isHeadless())
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    * added to check LaF for Linux
177    * 
178    * @return
179    */
180   public static boolean isLinux()
181   {
182     return (isLinux == null
183             ? (isLinux = (System.getProperty("os.name")
184                     .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    * Construct the value that depends on the system architecture. The methods
234    * setting the value for subsequent platforms are chained after this call and
235    * finalized with a {@link PlatformDependentValue#value() value()} call.
236    * 
237    * Example: {@code
238    *   Platform.forArch(120).forMac(114).forWin(112).forLinux(115).value();
239    * }
240    * 
241    * @param <T>
242    *          type of the value
243    * @param defaultValue
244    *          default value used if platform not determined
245    * @return platform dependent value wrapper object
246    */
247   public static <T> PlatformDependentValue<T> forArch(T defaultValue)
248   {
249     return new PlatformDependentValue<T>(defaultValue);
250   }
251
252   /**
253    * 
254    * @author mmwarowny
255    *
256    * @param <T>
257    *          type of the value
258    */
259   public static class PlatformDependentValue<T>
260   {
261     private T defaultValue = null;
262
263     private T macValue = null;
264
265     private T winValue = null;
266
267     private T linuxValue = null;
268
269     private T jsValue = null;
270
271     private T headlessValue = null;
272
273     private PlatformDependentValue(T value)
274     {
275       Objects.requireNonNull(value);
276       defaultValue = value;
277     }
278
279     /**
280      * Set the value used on Mac platform.
281      * 
282      * @param value
283      *          parameter value
284      * @return
285      */
286     public PlatformDependentValue<T> forMac(T value)
287     {
288       Objects.requireNonNull(value);
289       macValue = value;
290       return this;
291     }
292
293     /**
294      * Set the value used on Windows platform.
295      * 
296      * @param value
297      *          parameter value
298      * @return
299      */
300     public PlatformDependentValue<T> forWin(T value)
301     {
302       Objects.requireNonNull(value);
303       winValue = value;
304       return this;
305     }
306
307     /**
308      * Set the value used on Linux platform.
309      * 
310      * @param value
311      *          parameter value
312      * @return
313      */
314     public PlatformDependentValue<T> forLinux(T value)
315     {
316       Objects.requireNonNull(value);
317       linuxValue = value;
318       return this;
319     }
320
321     /**
322      * Set the value used on JS platform.
323      * 
324      * @param value
325      *          parameter value
326      * @return
327      */
328     public PlatformDependentValue<T> forJS(T value)
329     {
330       Objects.requireNonNull(value);
331       jsValue = value;
332       return this;
333     }
334
335     /**
336      * Set the value used on headless platform. The headless value takes
337      * precedence over other platforms if set.
338      * 
339      * @param value
340      *          parameter value
341      * @return
342      */
343     public PlatformDependentValue<T> forHeadless(T value)
344     {
345       Objects.requireNonNull(value);
346       headlessValue = value;
347       return this;
348     }
349
350     /**
351      * Get the value of the parameter respecting the platform. The headless
352      * platform takes precedence over any other platform if it has the value
353      * set.
354      * 
355      * @return parameter value depending on the platform
356      */
357     public T value()
358     {
359       if (headlessValue != null && isHeadless())
360         return headlessValue;
361       if (macValue != null && isMac())
362         return macValue;
363       if (winValue != null && isWin())
364         return winValue;
365       if (linuxValue != null && isLinux())
366         return linuxValue;
367       if (jsValue != null && isJS())
368         return jsValue;
369       return defaultValue;
370     }
371   }
372
373   /**
374    * 
375    * @return nominal maximum command line length for this platform
376    */
377   public static int getMaxCommandLineLength()
378   {
379     // TODO: determine nominal limits for most platforms.
380     return 2046; // this is the max length for a windows NT system.
381   }
382
383   /**
384    * Answers the input with every backslash replaced with a double backslash (an
385    * 'escaped' single backslash)
386    * 
387    * @param s
388    * @return
389    */
390   public static String escapeBackslashes(String s)
391   {
392     return s == null ? null : s.replace("\\", "\\\\");
393   }
394
395   /**
396    * Answers true if the mouse event has Meta-down (Command key on Mac) or
397    * Ctrl-down (on other o/s). Note this answers _false_ if the Ctrl key is
398    * pressed instead of the Meta/Cmd key on Mac. To test for Ctrl-pressed on
399    * Mac, you can use e.isPopupTrigger().
400    * 
401    * @param e
402    * @return
403    */
404   public static boolean isControlDown(MouseEvent e)
405   {
406     return isControlDown(e, isMac());
407   }
408
409   /**
410    * Overloaded version of method (to allow unit testing)
411    * 
412    * @param e
413    * @param aMac
414    * @return
415    */
416   protected static boolean isControlDown(MouseEvent e, boolean aMac)
417   {
418     //
419     // System.out.println(e.isPopupTrigger()
420     // + " " + ((SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0)
421     // + " " + e.isControlDown());
422     return (aMac
423             ? !e.isPopupTrigger()
424                     && (SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0
425             : e.isControlDown());
426   }
427
428   // BH: I don't know about that previous method. Here is what SwingJS uses.
429   // Notice the distinction in mouse events. (BUTTON3_MASK == META)
430   //
431   // private static boolean isPopupTrigger(int id, int mods, boolean isWin) {
432   // boolean rt = ((mods & InputEvent.BUTTON3_MASK) != 0);
433   // if (isWin) {
434   // if (id != MouseEvent.MOUSE_RELEASED)
435   // return false;
436   ////
437   //// // Oddly, Windows returns InputEvent.META_DOWN_MASK on release, though
438   //// // BUTTON3_DOWN_MASK for pressed. So here we just accept both.
439   ////
440   //// actually, we can use XXX_MASK, not XXX_DOWN_MASK and avoid this issue,
441   // because
442   //// J2S adds the appropriate extended (0x3FC0) and simple (0x3F) modifiers.
443   ////
444   // return rt;
445   // } else {
446   // // mac, linux, unix
447   // if (id != MouseEvent.MOUSE_PRESSED)
448   // return false;
449   // boolean lt = ((mods & InputEvent.BUTTON1_MASK) != 0);
450   // boolean ctrl = ((mods & InputEvent.CTRL_MASK) != 0);
451   // return rt || (ctrl && lt);
452   // }
453   // }
454   //
455
456   /**
457    * Windows (not Mac, Linux, or Unix) and right button to test for the
458    * right-mouse pressed event in Windows that would have opened a menu or a
459    * Mac.
460    * 
461    * @param e
462    * @return
463    */
464   public static boolean isWinRightButton(MouseEvent e)
465   {
466     // was !isAMac(), but that is true also for Linux and Unix and JS,
467
468     return isWin() && SwingUtilities.isRightMouseButton(e);
469   }
470
471   /**
472    * Windows (not Mac, Linux, or Unix) and middle button -- for mouse wheeling
473    * without pressing the button.
474    * 
475    * @param e
476    * @return
477    */
478   public static boolean isWinMiddleButton(MouseEvent e)
479   {
480     // was !isAMac(), but that is true also for Linux and Unix and JS
481     return isWin() && SwingUtilities.isMiddleMouseButton(e);
482   }
483
484   public static boolean allowMnemonics()
485   {
486     return !isMac();
487   }
488
489   public final static int TIME_RESET = 0;
490
491   public final static int TIME_MARK = 1;
492
493   public static final int TIME_SET = 2;
494
495   public static final int TIME_GET = 3;
496
497   public static long time, mark, set, duration;
498
499   /**
500    * typical usage:
501    * 
502    * Platform.timeCheck(null, Platform.TIME_MARK);
503    * 
504    * ...
505    * 
506    * Platform.timeCheck("some message", Platform.TIME_MARK);
507    * 
508    * reset...[set/mark]n...get
509    * 
510    * @param msg
511    * @param mode
512    */
513   public static void timeCheck(String msg, int mode)
514   {
515     long t = System.currentTimeMillis();
516     switch (mode)
517     {
518     case TIME_RESET:
519       time = mark = t;
520       duration = 0;
521       if (msg != null)
522       {
523         System.err.println("Platform: timer reset\t\t\t" + msg);
524       }
525       break;
526     case TIME_MARK:
527       if (set > 0)
528       {
529         // total time between set/mark points
530         duration += (t - set);
531       }
532       else
533       {
534         if (time == 0)
535         {
536           time = mark = t;
537         }
538         if (msg != null)
539         {
540           System.err.println("Platform: timer mark\t" + ((t - time) / 1000f)
541                   + "\t" + ((t - mark) / 1000f) + "\t" + msg);
542         }
543         mark = t;
544       }
545       break;
546     case TIME_SET:
547       set = t;
548       break;
549     case TIME_GET:
550       if (msg != null)
551       {
552         System.err.println("Platform: timer dur\t" + ((t - time) / 1000f)
553                 + "\t" + ((duration) / 1000f) + "\t" + msg);
554       }
555       set = 0;
556       break;
557     }
558   }
559
560   public static void cacheFileData(String path, Object data)
561   {
562     if (isJS && data != null)
563     {
564       jsutil.cachePathData(path, data);
565     }
566   }
567
568   public static void cacheFileData(File file)
569   {
570     if (isJS)
571     {
572       byte[] data = Platform.getFileBytes(file);
573       {
574         if (data != null)
575         {
576           cacheFileData(file.toString(), data);
577         }
578       }
579     }
580   }
581
582   public static byte[] getFileBytes(File f)
583   {
584     return (isJS && f != null ? jsutil.getBytes(f) : null);
585   }
586
587   public static byte[] getFileAsBytes(String fileStr)
588   {
589     if (isJS && fileStr != null)
590     {
591       byte[] bytes = (byte[]) jsutil.getFile(fileStr, false);
592       cacheFileData(fileStr, bytes);
593       return bytes;
594     }
595     return null;
596   }
597
598   public static String getFileAsString(String url)
599   {
600     if (isJS && url != null)
601     {
602       String ret = (String) jsutil.getFile(url, true);
603       cacheFileData(url, ret);
604       return ret;
605     }
606     return null;
607   }
608
609   public static boolean setFileBytes(File f, String urlstring)
610   {
611     if (isJS && f != null && urlstring != null)
612     {
613       @SuppressWarnings("unused")
614       byte[] bytes = getFileAsBytes(urlstring);
615       jsutil.setFileBytes(f, bytes);
616       return true;
617     }
618     return false;
619   }
620
621   public static void addJ2SBinaryType(String ext)
622   {
623     if (isJS)
624     {
625       jsutil.addBinaryFileType(ext);
626     }
627   }
628
629   /**
630    * Encode the URI using JavaScript encodeURIComponent
631    * 
632    * @param value
633    * @return encoded value
634    */
635   public static String encodeURI(String value)
636   {
637     /**
638      * @j2sNative value = encodeURIComponent(value);
639      */
640     return value;
641   }
642
643   /**
644    * Open the URL using a simple window call if this is JavaScript
645    * 
646    * @param url
647    * @return true if window has been opened
648    */
649   public static boolean openURL(String url) throws IOException
650   {
651     if (!isJS())
652     {
653       return false;
654     }
655     /**
656      * @j2sNative
657      * 
658      * 
659      *            window.open(url);
660      */
661     return true;
662   }
663
664   public static String getUniqueAppletID()
665   {
666     return (isJS ? (String) jsutil.getAppletAttribute("_uniqueId") : null);
667
668   }
669
670   /**
671    * Read the Info block for this applet.
672    * 
673    * @param prefix
674    *          "jalview_"
675    * @param p
676    * @return unique id for this applet
677    */
678   public static void readInfoProperties(String prefix, Properties p)
679   {
680     if (isJS)
681     {
682       String id = getUniqueAppletID();
683
684       String key = "";
685       String value = "";
686       @SuppressWarnings("unused")
687       Object info = jsutil.getAppletAttribute("__Info");
688       /**
689        * @j2sNative for (key in info) { value = info[key];
690        */
691
692       if (key.indexOf(prefix) == 0)
693       {
694         System.out.println("Platform id=" + id + " reading Info." + key
695                 + " = " + value);
696         p.put(key, value);
697
698       }
699
700       /**
701        * @j2sNative }
702        */
703     }
704   }
705
706   public static void setAjaxJSON(URL url)
707   {
708     if (isJS())
709     {
710       JSON.setAjax(url);
711     }
712   }
713
714   public static Object parseJSON(InputStream response)
715           throws IOException, ParseException
716   {
717     if (isJS())
718     {
719       return JSON.parse(response);
720     }
721
722     BufferedReader br = null;
723     try
724     {
725       br = new BufferedReader(new InputStreamReader(response, "UTF-8"));
726       return new JSONParser().parse(br);
727     } finally
728     {
729       if (br != null)
730       {
731         try
732         {
733           br.close();
734         } catch (IOException e)
735         {
736           // ignore
737         }
738       }
739     }
740   }
741
742   public static Object parseJSON(String json) throws ParseException
743   {
744     return (isJS() ? JSON.parse(json) : new JSONParser().parse(json));
745   }
746
747   public static Object parseJSON(Reader r)
748           throws IOException, ParseException
749   {
750     if (r == null)
751     {
752       return null;
753     }
754
755     if (!isJS())
756     {
757       return new JSONParser().parse(r);
758     }
759     // Using a file reader is not currently supported in SwingJS JavaScript
760
761     if (r instanceof FileReader)
762     {
763       throw new IOException(
764               "StringJS does not support FileReader parsing for JSON -- but it could...");
765     }
766     return JSON.parse(r);
767
768   }
769
770   /**
771    * Dump the input stream to an output file.
772    * 
773    * @param is
774    * @param outFile
775    * @throws IOException
776    *           if the file cannot be created or there is a problem reading the
777    *           input stream.
778    */
779   public static void streamToFile(InputStream is, File outFile)
780           throws IOException
781   {
782     if (isJS)
783     {
784       jsutil.setFileBytes(outFile, is);
785       return;
786     }
787     FileOutputStream fio = new FileOutputStream(outFile);
788     try
789     {
790       byte[] bb = new byte[32 * 1024];
791       int l;
792       while ((l = is.read(bb)) > 0)
793       {
794         fio.write(bb, 0, l);
795       }
796     } finally
797     {
798       fio.close();
799     }
800   }
801
802   /**
803    * Add a known domain that implements access-control-allow-origin:*
804    * 
805    * These should be reviewed periodically.
806    * 
807    * @param domain
808    *          for a service that is not allowing ajax
809    * 
810    * @author hansonr@stolaf.edu
811    * 
812    */
813   public static void addJ2SDirectDatabaseCall(String domain)
814   {
815
816     if (isJS)
817     {
818       jsutil.addDirectDatabaseCall(domain);
819       System.out.println(
820             "Platform adding known access-control-allow-origin * for domain "
821                     + domain);
822       /**
823        * @j2sNative
824        * 
825        *            J2S.addDirectDatabaseCall(domain);
826        */
827     }
828
829   }
830
831   /**
832    * Allow for URL-line command arguments. Untested.
833    * 
834    */
835   public static void getURLCommandArguments()
836   {
837       try {
838       /**
839        * Retrieve the first query field as command arguments to Jalview. Include
840        * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's
841        * __Info.args element to this value.
842        * 
843        * @j2sNative var a =
844        *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
845        *            + "?").split("?")[1].split("#")[0]); a && (System.out.println("URL arguments detected were "+a)) &&
846        *            (J2S.thisApplet.__Info.urlargs = a.split(" ")); 
847        *            (!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."));
848        */
849     } catch (Throwable t)
850     {
851     }
852   }
853
854   /**
855    * A (case sensitive) file path comparator that ignores the difference between
856    * / and \
857    * 
858    * @param path1
859    * @param path2
860    * @return
861    */
862   public static boolean pathEquals(String path1, String path2)
863   {
864     if (path1 == null)
865     {
866       return path2 == null;
867     }
868     if (path2 == null)
869     {
870       return false;
871     }
872     String p1 = path1.replace('\\', '/');
873     String p2 = path2.replace('\\', '/');
874     return p1.equals(p2);
875   }
876   ///////////// JAL-3253 Applet additions //////////////
877
878   /**
879    * Retrieve the object's embedded size from a div's style on a page if
880    * embedded in SwingJS.
881    * 
882    * @param frame
883    *          JFrame or JInternalFrame
884    * @param defaultWidth
885    *          use -1 to return null (no default size)
886    * @param defaultHeight
887    * @return the embedded dimensions or null (no default size or not embedded)
888    */
889   public static Dimension getDimIfEmbedded(Component frame,
890           int defaultWidth, int defaultHeight)
891   {
892     Dimension d = null;
893     if (isJS)
894     {
895       d = (Dimension) getEmbeddedAttribute(frame, "dim");
896     }
897     return (d == null && defaultWidth >= 0
898             ? new Dimension(defaultWidth, defaultHeight)
899             : d);
900
901   }
902
903   public static Regex newRegex(String regex)
904   {
905     return newRegex(regex, null);
906   }
907
908   public static Regex newRegex(String searchString, String replaceString)
909   {
910     ensureRegex();
911     return (replaceString == null ? new Regex(searchString)
912             : new Regex(searchString, replaceString));
913   }
914
915   public static Regex newRegexPerl(String code)
916   {
917     ensureRegex();
918     return Regex.perlCode(code);
919   }
920
921   /**
922    * Initialize Java debug logging. A representative sample -- adapt as desired.
923    */
924   public static void startJavaLogging()
925   {
926     /**
927      * @j2sIgnore
928      */
929     {
930       logClass("java.awt.EventDispatchThread", "java.awt.EventQueue",
931               "java.awt.Component", "java.awt.focus.Component",
932               "java.awt.event.Component",
933               "java.awt.focus.DefaultKeyboardFocusManager");
934     }
935   }
936
937   /**
938    * Initiate Java logging for a given class. Only for Java, not JavaScript;
939    * Allows debugging of complex event processing.
940    * 
941    * @param className
942    */
943   public static void logClass(String... classNames)
944   {
945     /**
946      * @j2sIgnore
947      * 
948      * 
949      */
950     {
951       Logger rootLogger = Logger.getLogger("");
952       rootLogger.setLevel(Level.ALL);
953       ConsoleHandler consoleHandler = new ConsoleHandler();
954       consoleHandler.setLevel(Level.ALL);
955       for (int i = classNames.length; --i >= 0;)
956       {
957         Logger logger = Logger.getLogger(classNames[i]);
958         logger.setLevel(Level.ALL);
959         logger.addHandler(consoleHandler);
960       }
961     }
962   }
963
964   /**
965    * load a resource -- probably a core file -- if and only if a particular
966    * class has not been instantialized. We use a String here because if we used
967    * a .class object, that reference itself would simply load the class, and we
968    * want the core package to include that as well.
969    * 
970    * @param resourcePath
971    * @param className
972    */
973   public static void loadStaticResource(String resourcePath,
974           String className)
975   {
976     if (isJS)
977     {
978       jsutil.loadResourceIfClassUnknown(resourcePath, className);
979     }
980   }
981
982   public static void ensureRegex()
983   {
984     if (isJS)
985     {
986       loadStaticResource("core/core_stevesoft.z.js",
987               "com.stevesoft.pat.Regex");
988     }
989   }
990
991   /**
992    * Set the "app" property of the HTML5 applet object, for example,
993    * "testApplet.app", to point to the Jalview instance. This will be the object
994    * that page developers use that is similar to the original Java applet object
995    * that was accessed via LiveConnect.
996    * 
997    * @param j
998    */
999   public static void setAppClass(Object j)
1000   {
1001     if (isJS)
1002     {
1003       jsutil.setAppClass(j);
1004     }
1005   }
1006
1007   /**
1008    *
1009    * If this frame is embedded in a web page, return a known type.
1010    * 
1011    * @param frame
1012    *          a JFrame or JInternalFrame
1013    * @param type
1014    *          "name", "node", "init", "dim", or any DOM attribute, such as "id"
1015    * @return null if frame is not embedded.
1016    */
1017   public static Object getEmbeddedAttribute(Component frame, String type)
1018   {
1019     return (isJS ? jsutil.getEmbeddedAttribute(frame, type) : null);
1020   }
1021
1022   public static void stackTrace()
1023   {
1024     try
1025     {
1026       throw new NullPointerException();
1027     } catch (Exception e)
1028     {
1029       e.printStackTrace();
1030     }
1031
1032   }
1033
1034   public static URL getDocumentBase()
1035   {
1036     return (isJS ? jsutil.getDocumentBase() : null);
1037   }
1038
1039   public static URL getCodeBase()
1040   {
1041     return (isJS ? jsutil.getCodeBase() : null);
1042   }
1043
1044   public static String getUserPath(String subpath)
1045   {
1046     char sep = File.separatorChar;
1047     return System.getProperty("user.home") + sep
1048             + subpath.replace('/', sep);
1049   }
1050
1051   /**
1052    * This method enables checking if a cached file has exceeded a certain
1053    * threshold(in days)
1054    * 
1055    * @param file
1056    *          the cached file
1057    * @param noOfDays
1058    *          the threshold in days
1059    * @return
1060    */
1061   public static boolean isFileOlderThanThreshold(File file, int noOfDays)
1062   {
1063     if (isJS())
1064     {
1065       // not meaningful in SwingJS -- this is a session-specific temp file. It
1066       // doesn't have a timestamp.
1067       return false;
1068     }
1069     Path filePath = file.toPath();
1070     BasicFileAttributes attr;
1071     int diffInDays = 0;
1072     try
1073     {
1074       attr = Files.readAttributes(filePath, BasicFileAttributes.class);
1075       diffInDays = (int) ((new Date().getTime()
1076               - attr.lastModifiedTime().toMillis())
1077               / (1000 * 60 * 60 * 24));
1078       // System.out.println("Diff in days : " + diffInDays);
1079     } catch (IOException e)
1080     {
1081       e.printStackTrace();
1082     }
1083     return noOfDays <= diffInDays;
1084   }
1085
1086   /**
1087    * Get the leading integer part of a string that begins with an integer.
1088    * 
1089    * @param input
1090    *          - the string input to process
1091    * @param failValue
1092    *          - value returned if unsuccessful
1093    * @return
1094    */
1095   public static int getLeadingIntegerValue(String input, int failValue)
1096   {
1097     if (input == null)
1098     {
1099       return failValue;
1100     }
1101     if (isJS)
1102     {
1103       int val = /** @j2sNative 1 ? parseInt(input) : */
1104               0;
1105       return (val == val + 0 ? val : failValue);
1106     }
1107     // JavaScript does not support Regex ? lookahead
1108     String[] parts = input.split("(?=\\D)(?<=\\d)");
1109     if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
1110     {
1111       return Integer.valueOf(parts[0]);
1112     }
1113     return failValue;
1114   }
1115
1116   public static Map<String, Object> getAppletInfoAsMap()
1117   {
1118     return (isJS ? jsutil.getAppletInfoAsMap() : null);
1119   }
1120
1121   /**
1122    * Get the SwingJS applet ID and combine that with the frameType
1123    * 
1124    * @param frameType
1125    *          "alignment", "desktop", etc., or null
1126    * @return
1127    */
1128   public static String getAppID(String frameType)
1129   {
1130
1131     String id = Jalview.getInstance().j2sAppletID;
1132     if (id == null)
1133     {
1134       Jalview.getInstance().j2sAppletID = id = (isJS
1135               ? (String) jsutil.getAppletAttribute("_id")
1136               : "jalview");
1137     }
1138     return id + (frameType == null ? "" : "-" + frameType);
1139   }
1140
1141   /**
1142    * Option to avoid unnecessary seeking of nonexistent resources in JavaScript.
1143    * Works in Java as well.
1144    * 
1145    * @param loc
1146    * @return
1147    */
1148   public static Locale getLocaleOrNone(Locale loc)
1149   {
1150     return (isJS && loc.getLanguage() == "en" ? new Locale("") : loc);
1151   }
1152
1153   /**
1154    * From UrlDownloadClient; trivial in JavaScript; painful in Java.
1155    * 
1156    * @param urlstring
1157    * @param outfile
1158    * @throws IOException
1159    */
1160   public static void download(String urlstring, String outfile)
1161           throws IOException
1162   {
1163     Path temp = null;
1164     try (InputStream is = new URL(urlstring).openStream())
1165     {
1166       if (isJS)
1167       { // so much easier!
1168         streamToFile(is, new File(outfile));
1169         return;
1170       }
1171       temp = Files.createTempFile(".jalview_", ".tmp");
1172       try (FileOutputStream fos = new FileOutputStream(temp.toString());
1173               ReadableByteChannel rbc = Channels.newChannel(is))
1174       {
1175         fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
1176         // copy tempfile to outfile once our download completes
1177         // incase something goes wrong
1178         Files.copy(temp, Paths.get(outfile),
1179                 StandardCopyOption.REPLACE_EXISTING);
1180       }
1181     } catch (IOException e)
1182     {
1183       throw e;
1184     } finally
1185     {
1186       try
1187       {
1188         if (temp != null)
1189         {
1190           Files.deleteIfExists(temp);
1191         }
1192       } catch (IOException e)
1193       {
1194         System.out.println("Exception while deleting download temp file: "
1195                 + e.getMessage());
1196       }
1197     }
1198   }
1199 }