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