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