Merge branch 'releases/Release_2_11_3_Branch'
[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.Toolkit;
24 import java.awt.event.MouseEvent;
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.FileReader;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.Reader;
33 import java.net.URL;
34 import java.util.Properties;
35
36 import javax.swing.SwingUtilities;
37
38 import org.json.simple.parser.JSONParser;
39 import org.json.simple.parser.ParseException;
40
41 import jalview.javascript.json.JSON;
42
43 /**
44  * System platform information used by Applet and Application
45  * 
46  * @author Jim Procter
47  */
48 public class Platform
49 {
50
51   private static boolean isJS = /** @j2sNative true || */
52           false;
53
54   private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
55           isWin = null, isLinux = null;
56
57   private static Boolean isHeadless = null;
58
59   // If launched from CLI with launcher script then -DCOLUMNWIDTH is set
60   private static final int CONSOLEWIDTH;
61
62   private static final String CONSOLEWIDTHPROPERTY = "CONSOLEWIDTH";
63
64   static
65   {
66     int cw = 80;
67     if (System.getProperty(CONSOLEWIDTHPROPERTY) != null
68             && System.getProperty(CONSOLEWIDTHPROPERTY).length() > 0)
69     {
70       try
71       {
72         cw = Integer.parseInt(System.getProperty(CONSOLEWIDTHPROPERTY));
73       } catch (NumberFormatException e)
74       {
75       }
76     }
77     CONSOLEWIDTH = cw;
78   }
79
80   /**
81    * added to group mouse events into Windows and nonWindows (mac, unix, linux)
82    * 
83    * @return
84    */
85   public static boolean isMac()
86   {
87     return (isMac == null
88             ? (isMac = (System.getProperty("os.name").indexOf("Mac") >= 0))
89             : isMac);
90   }
91
92   /**
93    * added to group mouse events into Windows and nonWindows (mac, unix, linux)
94    * 
95    * @return
96    */
97   public static boolean isWin()
98   {
99     return (isWin == null
100             ? (isWin = (System.getProperty("os.name").indexOf("Win") >= 0))
101             : isWin);
102   }
103
104   /**
105    * added to check LaF for Linux
106    * 
107    * @return
108    */
109   public static boolean isLinux()
110   {
111     return (isLinux == null
112             ? (isLinux = (System.getProperty("os.name")
113                     .indexOf("Linux") >= 0))
114             : isLinux);
115   }
116
117   /**
118    * 
119    * @return true if HTML5 JavaScript
120    */
121   public static boolean isJS()
122   {
123     return isJS;
124   }
125
126   /**
127    * sorry folks - Macs really are different
128    * 
129    * BH: disabled for SwingJS -- will need to check key-press issues
130    * 
131    * @return true if we do things in a special way.
132    */
133   public static boolean isAMacAndNotJS()
134   {
135     return (isNoJSMac == null ? (isNoJSMac = !isJS && isMac()) : isNoJSMac);
136   }
137
138   /**
139    * Check if we are on a Microsoft plaform...
140    * 
141    * @return true if we have to cope with another platform variation
142    */
143   public static boolean isWindowsAndNotJS()
144   {
145     return (isNoJSWin == null ? (isNoJSWin = !isJS && isWin()) : isNoJSWin);
146   }
147
148   /**
149    * 
150    * @return true if we are running in non-interactive no UI mode
151    */
152   public static boolean isHeadless()
153   {
154     if (isHeadless == null)
155     {
156       isHeadless = "true".equals(System.getProperty("java.awt.headless"));
157     }
158     return isHeadless;
159   }
160
161   /**
162    * 
163    * @return nominal maximum command line length for this platform
164    */
165   public static int getMaxCommandLineLength()
166   {
167     // TODO: determine nominal limits for most platforms.
168     return 2046; // this is the max length for a windows NT system.
169   }
170
171   /**
172    * Answers the input with every backslash replaced with a double backslash (an
173    * 'escaped' single backslash)
174    * 
175    * @param s
176    * @return
177    */
178   public static String escapeBackslashes(String s)
179   {
180     return s == null ? null : s.replace("\\", "\\\\");
181   }
182
183   /**
184    * Answers true if the mouse event has Meta-down (Command key on Mac) or
185    * Ctrl-down (on other o/s). Note this answers _false_ if the Ctrl key is
186    * pressed instead of the Meta/Cmd key on Mac. To test for Ctrl-pressed on
187    * Mac, you can use e.isPopupTrigger().
188    * 
189    * @param e
190    * @return
191    */
192   public static boolean isControlDown(MouseEvent e)
193   {
194     return isControlDown(e, isMac());
195   }
196
197   /**
198    * Overloaded version of method (to allow unit testing)
199    * 
200    * @param e
201    * @param aMac
202    * @return
203    */
204   protected static boolean isControlDown(MouseEvent e, boolean aMac)
205   {
206     if (!aMac)
207     {
208       return e.isControlDown();
209
210       // Jalview 2.11 code below: above is as amended for JalviewJS
211       // /*
212       // * answer false for right mouse button
213       // */
214       // if (e.isPopupTrigger())
215       // {
216       // return false;
217       // }
218       // return
219       // (jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx() //
220       // .getMenuShortcutKeyMaskEx()
221       // & jalview.util.ShortcutKeyMaskExWrapper
222       // .getModifiersEx(e)) != 0; // getModifiers()) != 0;
223     }
224     // answer false for right mouse button
225     // shortcut key will be META for a Mac
226     return !e.isPopupTrigger()
227             && (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
228                     & e.getModifiers()) != 0;
229     // could we use e.isMetaDown() here?
230   }
231
232   // BH: I don't know about that previous method. Here is what SwingJS uses.
233   // Notice the distinction in mouse events. (BUTTON3_MASK == META)
234   //
235   // private static boolean isPopupTrigger(int id, int mods, boolean isWin) {
236   // boolean rt = ((mods & InputEvent.BUTTON3_MASK) != 0);
237   // if (isWin) {
238   // if (id != MouseEvent.MOUSE_RELEASED)
239   // return false;
240   ////
241   //// // Oddly, Windows returns InputEvent.META_DOWN_MASK on release, though
242   //// // BUTTON3_DOWN_MASK for pressed. So here we just accept both.
243   ////
244   //// actually, we can use XXX_MASK, not XXX_DOWN_MASK and avoid this issue,
245   // because
246   //// J2S adds the appropriate extended (0x3FC0) and simple (0x3F) modifiers.
247   ////
248   // return rt;
249   // } else {
250   // // mac, linux, unix
251   // if (id != MouseEvent.MOUSE_PRESSED)
252   // return false;
253   // boolean lt = ((mods & InputEvent.BUTTON1_MASK) != 0);
254   // boolean ctrl = ((mods & InputEvent.CTRL_MASK) != 0);
255   // return rt || (ctrl && lt);
256   // }
257   // }
258   //
259
260   /**
261    * Windows (not Mac, Linux, or Unix) and right button to test for the
262    * right-mouse pressed event in Windows that would have opened a menu or a
263    * Mac.
264    * 
265    * @param e
266    * @return
267    */
268   public static boolean isWinRightButton(MouseEvent e)
269   {
270     // was !isAMac(), but that is true also for Linux and Unix and JS,
271
272     return isWin() && SwingUtilities.isRightMouseButton(e);
273   }
274
275   /**
276    * Windows (not Mac, Linux, or Unix) and middle button -- for mouse wheeling
277    * without pressing the button.
278    * 
279    * @param e
280    * @return
281    */
282   public static boolean isWinMiddleButton(MouseEvent e)
283   {
284     // was !isAMac(), but that is true also for Linux and Unix and JS
285     return isWin() && SwingUtilities.isMiddleMouseButton(e);
286   }
287
288   public static boolean allowMnemonics()
289   {
290     return !isMac();
291   }
292
293   public final static int TIME_RESET = 0;
294
295   public final static int TIME_MARK = 1;
296
297   public static final int TIME_SET = 2;
298
299   public static final int TIME_GET = 3;
300
301   public static long time, mark, set, duration;
302
303   public static void timeCheck(String msg, int mode)
304   {
305     long t = System.currentTimeMillis();
306     switch (mode)
307     {
308     case TIME_RESET:
309       time = mark = t;
310       if (msg != null)
311       {
312         jalview.bin.Console.errPrintln("Platform: timer reset\t\t\t" + msg);
313       }
314       break;
315     case TIME_MARK:
316       if (set > 0)
317       {
318         duration += (t - set);
319       }
320       else
321       {
322         if (time == 0)
323         {
324           time = mark = t;
325         }
326         if (msg != null)
327         {
328           jalview.bin.Console.errPrintln(
329                   "Platform: timer mark\t" + ((t - time) / 1000f) + "\t"
330                           + ((t - mark) / 1000f) + "\t" + msg);
331         }
332         mark = t;
333       }
334       break;
335     case TIME_SET:
336       set = t;
337       break;
338     case TIME_GET:
339       if (msg != null)
340       {
341         jalview.bin.Console
342                 .errPrintln("Platform: timer dur\t" + ((t - time) / 1000f)
343                         + "\t" + ((duration) / 1000f) + "\t" + msg);
344       }
345       set = 0;
346       break;
347     }
348   }
349
350   public static void cacheFileData(String path, Object data)
351   {
352     if (!isJS() || data == null)
353     {
354       return;
355     }
356     /**
357      * @j2sNative
358      * 
359      *            swingjs.JSUtil.cacheFileData$S$O(path, data);
360      * 
361      */
362   }
363
364   public static void cacheFileData(File file)
365   {
366     byte[] data;
367     if (!isJS() || (data = Platform.getFileBytes(file)) == null)
368     {
369       return;
370     }
371     cacheFileData(file.toString(), data);
372   }
373
374   public static byte[] getFileBytes(File f)
375   {
376     return /** @j2sNative f && swingjs.JSUtil.getFileAsBytes$O(f) || */
377     null;
378   }
379
380   public static byte[] getFileAsBytes(String fileStr)
381   {
382     byte[] bytes = null;
383     // BH 2018 hack for no support for access-origin
384     /**
385      * @j2sNative bytes = swingjs.JSUtil.getFileAsBytes$O(fileStr)
386      */
387     cacheFileData(fileStr, bytes);
388     return bytes;
389   }
390
391   @SuppressWarnings("unused")
392   public static String getFileAsString(String url)
393   {
394     String ret = null;
395     /**
396      * @j2sNative
397      * 
398      *            ret = swingjs.JSUtil.getFileAsString$S(url);
399      * 
400      * 
401      */
402     cacheFileData(url, ret);
403     return ret;
404   }
405
406   public static boolean setFileBytes(File f, String urlstring)
407   {
408     if (!isJS())
409     {
410       return false;
411     }
412     @SuppressWarnings("unused")
413     byte[] bytes = getFileAsBytes(urlstring);
414     // TODO temporary doubling of ç§˜bytes and _bytes;
415     // just remove _bytes when new transpiler has been installed
416     /**
417      * @j2sNative f.\u79d8bytes = f._bytes = bytes;
418      */
419     return true;
420   }
421
422   public static void addJ2SBinaryType(String ext)
423   {
424     /**
425      * @j2sNative
426      * 
427      *            J2S._binaryTypes.push("." + ext + "?");
428      * 
429      */
430   }
431
432   /**
433    * Encode the URI using JavaScript encodeURIComponent
434    * 
435    * @param value
436    * @return encoded value
437    */
438   public static String encodeURI(String value)
439   {
440     /**
441      * @j2sNative value = encodeURIComponent(value);
442      */
443     return value;
444   }
445
446   /**
447    * Open the URL using a simple window call if this is JavaScript
448    * 
449    * @param url
450    * @return true if window has been opened
451    */
452   public static boolean openURL(String url)
453   {
454     if (!isJS())
455     {
456       return false;
457     }
458     /**
459      * @j2sNative
460      * 
461      * 
462      *            window.open(url);
463      */
464     return true;
465   }
466
467   public static String getUniqueAppletID()
468   {
469     /**
470      * @j2sNative return swingjs.JSUtil.getApplet$()._uniqueId;
471      *
472      */
473     return null;
474
475   }
476
477   /**
478    * Read the Info block for this applet.
479    * 
480    * @param prefix
481    *          "jalview_"
482    * @param p
483    * @return unique id for this applet
484    */
485   public static void readInfoProperties(String prefix, Properties p)
486   {
487     if (!isJS())
488     {
489       return;
490     }
491     String id = getUniqueAppletID();
492     String key = "", value = "";
493     /**
494      * @j2sNative var info = swingjs.JSUtil.getApplet$().__Info || {}; for (var
495      *            key in info) { if (key.indexOf(prefix) == 0) { value = "" +
496      *            info[key];
497      */
498
499     jalview.bin.Console.outPrintln(
500             "Platform id=" + id + " reading Info." + key + " = " + value);
501     p.put(id + "_" + key, value);
502
503     /**
504      * @j2sNative
505      * 
506      * 
507      *            } }
508      */
509   }
510
511   public static void setAjaxJSON(URL url)
512   {
513     if (isJS())
514     {
515       JSON.setAjax(url);
516     }
517   }
518
519   public static Object parseJSON(InputStream response)
520           throws IOException, ParseException
521   {
522     if (isJS())
523     {
524       return JSON.parse(response);
525     }
526
527     BufferedReader br = null;
528     try
529     {
530       br = new BufferedReader(new InputStreamReader(response, "UTF-8"));
531       return new JSONParser().parse(br);
532     } finally
533     {
534       if (br != null)
535       {
536         try
537         {
538           br.close();
539         } catch (IOException e)
540         {
541           // ignore
542         }
543       }
544     }
545   }
546
547   public static Object parseJSON(String json) throws ParseException
548   {
549     return (isJS() ? JSON.parse(json) : new JSONParser().parse(json));
550   }
551
552   public static Object parseJSON(Reader r)
553           throws IOException, ParseException
554   {
555     if (r == null)
556     {
557       return null;
558     }
559
560     if (!isJS())
561     {
562       return new JSONParser().parse(r);
563     }
564     // Using a file reader is not currently supported in SwingJS JavaScript
565
566     if (r instanceof FileReader)
567     {
568       throw new IOException(
569               "StringJS does not support FileReader parsing for JSON -- but it could...");
570     }
571     return JSON.parse(r);
572
573   }
574
575   /**
576    * Dump the input stream to an output file.
577    * 
578    * @param is
579    * @param outFile
580    * @throws IOException
581    *           if the file cannot be created or there is a problem reading the
582    *           input stream.
583    */
584   public static void streamToFile(InputStream is, File outFile)
585           throws IOException
586   {
587     if (isJS() && /**
588                    * @j2sNative outFile.setBytes$O && outFile.setBytes$O(is) &&
589                    */
590             true)
591     {
592       return;
593     }
594     FileOutputStream fio = new FileOutputStream(outFile);
595     try
596     {
597       byte[] bb = new byte[32 * 1024];
598       int l;
599       while ((l = is.read(bb)) > 0)
600       {
601         fio.write(bb, 0, l);
602       }
603     } finally
604     {
605       fio.close();
606     }
607   }
608
609   /**
610    * Add a known domain that implements access-control-allow-origin:*
611    * 
612    * These should be reviewed periodically.
613    * 
614    * @param domain
615    *          for a service that is not allowing ajax
616    * 
617    * @author hansonr@stolaf.edu
618    * 
619    */
620   public static void addJ2SDirectDatabaseCall(String domain)
621   {
622
623     if (isJS())
624     {
625       jalview.bin.Console.outPrintln(
626               "Platform adding known access-control-allow-origin * for domain "
627                       + domain);
628       /**
629        * @j2sNative
630        * 
631        *            J2S.addDirectDatabaseCall(domain);
632        */
633     }
634
635   }
636
637   public static void getURLCommandArguments()
638   {
639     try
640     {
641       /**
642        * Retrieve the first query field as command arguments to Jalview. Include
643        * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's
644        * __Info.args element to this value.
645        * 
646        * @j2sNative var a =
647        *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
648        *            + "?").split("?")[1].split("#")[0]); a &&
649        *            (jalview.bin.Console.outPrintln("URL arguments detected were
650        *            "+a)) && (J2S.thisApplet.__Info.urlargs = a.split(" "));
651        *            (!J2S.thisApplet.__Info.args || J2S.thisApplet.__Info.args
652        *            == "" || J2S.thisApplet.__Info.args == "??") &&
653        *            (J2S.thisApplet.__Info.args = a) &&
654        *            (jalview.bin.Console.outPrintln("URL arguments were passed
655        *            to J2S main."));
656        */
657     } catch (Throwable t)
658     {
659     }
660   }
661
662   /**
663    * A (case sensitive) file path comparator that ignores the difference between
664    * / and \
665    * 
666    * @param path1
667    * @param path2
668    * @return
669    */
670   public static boolean pathEquals(String path1, String path2)
671   {
672     if (path1 == null)
673     {
674       return path2 == null;
675     }
676     if (path2 == null)
677     {
678       return false;
679     }
680     String p1 = path1.replace('\\', '/');
681     String p2 = path2.replace('\\', '/');
682     return p1.equals(p2);
683   }
684
685   /**
686    * If started on command line using launch script, return the console width
687    */
688   public static int consoleWidth()
689   {
690     return CONSOLEWIDTH;
691   }
692 }