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