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