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