JAL-3210 JAL-3263 redo 'Mandarin' character for JS-develop pre-applet
[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       return e.isControlDown();
175
176       // Jalview 2.11 code below: above is as amended for JalviewJS
177       // /*
178       // * answer false for right mouse button
179       // */
180       // if (e.isPopupTrigger())
181       // {
182       // return false;
183       // }
184       // return
185       // (jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx() //
186       // .getMenuShortcutKeyMaskEx()
187       // & jalview.util.ShortcutKeyMaskExWrapper
188       // .getModifiersEx(e)) != 0; // getModifiers()) != 0;
189     }
190     // answer false for right mouse button
191     // shortcut key will be META for a Mac
192     return !e.isPopupTrigger()
193             && (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
194                     & e.getModifiers()) != 0;
195     // could we use e.isMetaDown() here?
196   }
197
198   // BH: I don't know about that previous method. Here is what SwingJS uses.
199   // Notice the distinction in mouse events. (BUTTON3_MASK == META)
200   //
201   // private static boolean isPopupTrigger(int id, int mods, boolean isWin) {
202   // boolean rt = ((mods & InputEvent.BUTTON3_MASK) != 0);
203   // if (isWin) {
204   // if (id != MouseEvent.MOUSE_RELEASED)
205   // return false;
206   ////
207   //// // Oddly, Windows returns InputEvent.META_DOWN_MASK on release, though
208   //// // BUTTON3_DOWN_MASK for pressed. So here we just accept both.
209   ////
210   //// actually, we can use XXX_MASK, not XXX_DOWN_MASK and avoid this issue,
211   // because
212   //// J2S adds the appropriate extended (0x3FC0) and simple (0x3F) modifiers.
213   ////
214   // return rt;
215   // } else {
216   // // mac, linux, unix
217   // if (id != MouseEvent.MOUSE_PRESSED)
218   // return false;
219   // boolean lt = ((mods & InputEvent.BUTTON1_MASK) != 0);
220   // boolean ctrl = ((mods & InputEvent.CTRL_MASK) != 0);
221   // return rt || (ctrl && lt);
222   // }
223   // }
224   //
225
226   /**
227    * Windows (not Mac, Linux, or Unix) and right button to test for the
228    * right-mouse pressed event in Windows that would have opened a menu or a
229    * Mac.
230    * 
231    * @param e
232    * @return
233    */
234   public static boolean isWinRightButton(MouseEvent e)
235   {
236     // was !isAMac(), but that is true also for Linux and Unix and JS,
237
238     return isWin() && SwingUtilities.isRightMouseButton(e);
239   }
240
241   /**
242    * Windows (not Mac, Linux, or Unix) and middle button -- for mouse wheeling
243    * without pressing the button.
244    * 
245    * @param e
246    * @return
247    */
248   public static boolean isWinMiddleButton(MouseEvent e)
249   {
250     // was !isAMac(), but that is true also for Linux and Unix and JS
251     return isWin() && SwingUtilities.isMiddleMouseButton(e);
252   }
253
254   public static boolean allowMnemonics()
255   {
256     return !isMac();
257   }
258
259   public final static int TIME_RESET = 0;
260
261   public final static int TIME_MARK = 1;
262
263   public static final int TIME_SET = 2;
264
265   public static final int TIME_GET = 3;
266
267   public static long time, mark, set, duration;
268
269   public static void timeCheck(String msg, int mode)
270   {
271     long t = System.currentTimeMillis();
272     switch (mode)
273     {
274     case TIME_RESET:
275       time = mark = t;
276       if (msg != null)
277       {
278         System.err.println("Platform: timer reset\t\t\t" + msg);
279       }
280       break;
281     case TIME_MARK:
282       if (set > 0)
283       {
284         duration += (t - set);
285       }
286       else
287       {
288         if (time == 0)
289         {
290           time = mark = t;
291         }
292         if (msg != null)
293         {
294           System.err.println("Platform: timer mark\t" + ((t - time) / 1000f)
295                   + "\t" + ((t - mark) / 1000f) + "\t" + msg);
296         }
297         mark = t;
298       }
299       break;
300     case TIME_SET:
301       set = t;
302       break;
303     case TIME_GET:
304       if (msg != null)
305       {
306         System.err.println("Platform: timer dur\t" + ((t - time) / 1000f)
307                 + "\t" + ((duration) / 1000f) + "\t" + msg);
308       }
309       set = 0;
310       break;
311     }
312   }
313
314   public static void cacheFileData(String path, Object data)
315   {
316     if (!isJS() || data == null)
317     {
318       return;
319     }
320     /**
321      * @j2sNative
322      * 
323      *            swingjs.JSUtil.cacheFileData$S$O(path, data);
324      * 
325      */
326   }
327
328   public static void cacheFileData(File file)
329   {
330     byte[] data;
331     if (!isJS() || (data = Platform.getFileBytes(file)) == null)
332     {
333       return;
334     }
335     cacheFileData(file.toString(), data);
336   }
337
338   public static byte[] getFileBytes(File f)
339   {
340     // TODO temporary doubling of ç§˜bytes and _bytes;
341     // just remove _bytes when new transpiler has been installed
342     return /** @j2sNative f && (f.\u79d8bytes || f._bytes) || */
343     null;
344   }
345
346   public static byte[] getFileAsBytes(String fileStr)
347   {
348     byte[] bytes = null;
349     // BH 2018 hack for no support for access-origin
350     /**
351      * @j2sNative bytes = swingjs.JSUtil.getFileAsBytes$O(fileStr)
352      */
353     cacheFileData(fileStr, bytes);
354     return bytes;
355   }
356
357   @SuppressWarnings("unused")
358   public static String getFileAsString(String url)
359   {
360     String ret = null;
361     /**
362      * @j2sNative
363      * 
364      *            ret = swingjs.JSUtil.getFileAsString$S(url);
365      * 
366      * 
367      */
368     cacheFileData(url, ret);
369     return ret;
370   }
371
372   public static boolean setFileBytes(File f, String urlstring)
373   {
374     if (!isJS())
375     {
376       return false;
377     }
378     @SuppressWarnings("unused")
379     byte[] bytes = getFileAsBytes(urlstring);
380     // TODO temporary doubling of ç§˜bytes and _bytes;
381     // just remove _bytes when new transpiler has been installed
382     /**
383      * @j2sNative f.\u79d8bytes = f._bytes = bytes;
384      */
385     return true;
386   }
387
388   public static void addJ2SBinaryType(String ext)
389   {
390     /**
391      * @j2sNative
392      * 
393      *            J2S._binaryTypes.push("." + ext + "?");
394      * 
395      */
396   }
397
398   /**
399    * Encode the URI using JavaScript encodeURIComponent
400    * 
401    * @param value
402    * @return encoded value
403    */
404   public static String encodeURI(String value)
405   {
406     /**
407      * @j2sNative value = encodeURIComponent(value);
408      */
409     return value;
410   }
411
412   /**
413    * Open the URL using a simple window call if this is JavaScript
414    * 
415    * @param url
416    * @return true if window has been opened
417    */
418   public static boolean openURL(String url)
419   {
420     if (!isJS())
421     {
422       return false;
423     }
424     /**
425      * @j2sNative
426      * 
427      * 
428      *            window.open(url);
429      */
430     return true;
431   }
432
433   public static String getUniqueAppletID()
434   {
435     @SuppressWarnings("unused")
436     ThreadGroup g = Thread.currentThread().getThreadGroup();
437     /**
438      * @j2sNative return g.\u79d8html5Applet._uniqueId;
439      *
440      */
441     return null;
442
443   }
444
445   /**
446    * Read the Info block for this applet.
447    * 
448    * @param prefix
449    *          "jalview_"
450    * @param p
451    * @return unique id for this applet
452    */
453   public static void readInfoProperties(String prefix, Properties p)
454   {
455     if (!isJS())
456     {
457       return;
458     }
459     @SuppressWarnings("unused")
460     ThreadGroup g = Thread.currentThread().getThreadGroup();
461     String id = getUniqueAppletID();
462     String key = "", value = "";
463     /**
464      * @j2sNative var info = g.\u79d8html5Applet.__Info || {}; for (var key in info) {
465      *            if (key.indexOf(prefix) == 0) { value = "" + info[key];
466      */
467
468     System.out.println(
469             "Platform id=" + id + " reading Info." + key + " = " + value);
470     p.put(id + "_" + key, value);
471
472     /**
473      * @j2sNative
474      * 
475      * 
476      *            } }
477      */
478   }
479
480   public static void setAjaxJSON(URL url)
481   {
482     if (isJS())
483     {
484       JSON.setAjax(url);
485     }
486   }
487
488   public static Object parseJSON(InputStream response)
489           throws IOException, ParseException
490   {
491     if (isJS())
492     {
493       return JSON.parse(response);
494     }
495
496     BufferedReader br = null;
497     try
498     {
499       br = new BufferedReader(new InputStreamReader(response, "UTF-8"));
500       return new JSONParser().parse(br);
501     } finally
502     {
503       if (br != null)
504       {
505         try
506         {
507           br.close();
508         } catch (IOException e)
509         {
510           // ignore
511         }
512       }
513     }
514   }
515
516   public static Object parseJSON(String json) throws ParseException
517   {
518     return (isJS() ? JSON.parse(json)
519             : new JSONParser().parse(json));
520   }
521
522   public static Object parseJSON(Reader r)
523           throws IOException, ParseException
524   {
525     if (r == null)
526     {
527       return null;
528     }
529
530     if (!isJS())
531     {
532       return new JSONParser().parse(r);
533     }
534     // Using a file reader is not currently supported in SwingJS JavaScript
535
536     if (r instanceof FileReader)
537     {
538       throw new IOException(
539               "StringJS does not support FileReader parsing for JSON -- but it could...");
540     }
541     return JSON.parse(r);
542
543   }
544
545   /**
546    * Dump the input stream to an output file.
547    * 
548    * @param is
549    * @param outFile
550    * @throws IOException
551    *           if the file cannot be created or there is a problem reading the
552    *           input stream.
553    */
554   public static void streamToFile(InputStream is, File outFile)
555           throws IOException
556   {
557     FileOutputStream fio = new FileOutputStream(outFile);
558     try
559     {
560       if (isJS()
561               && /**
562                   * @j2sNative outFile.setBytes$O && outFile.setBytes$O(is) &&
563                   */
564               true)
565       {
566         return;
567       }
568       byte[] bb = new byte[32 * 1024];
569       int l;
570       while ((l = is.read(bb)) > 0)
571       {
572         fio.write(bb, 0, l);
573       }
574     } finally
575     {
576       fio.close();
577     }
578   }
579
580   /**
581    * Add a known domain that implements access-control-allow-origin:*
582    * 
583    * These should be reviewed periodically.
584    * 
585    * @param domain
586    *          for a service that is not allowing ajax
587    * 
588    * @author hansonr@stolaf.edu
589    * 
590    */
591   public static void addJ2SDirectDatabaseCall(String domain)
592   {
593
594     if (isJS())
595     {
596       System.out.println(
597             "Platform adding known access-control-allow-origin * for domain "
598                     + domain);
599       /**
600        * @j2sNative
601        * 
602        *            J2S.addDirectDatabaseCall(domain);
603        */
604     }
605
606   }
607
608   public static void getURLCommandArguments()
609   {
610
611     /**
612      * Retrieve the first query field as command arguments to Jalview. Include
613      * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's __Info.args
614      * element to this value.
615      * 
616      * @j2sNative var a =
617      *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
618      *            + "?").split("?")[1].split("#")[0]); a &&
619      *            (J2S.thisApplet.__Info.args = a.split(" "));
620      */
621
622   }
623
624   /**
625    * A (case sensitive) file path comparator that ignores the difference between /
626    * and \
627    * 
628    * @param path1
629    * @param path2
630    * @return
631    */
632   public static boolean pathEquals(String path1, String path2)
633   {
634     if (path1 == null)
635     {
636       return path2 == null;
637     }
638     if (path2 == null)
639     {
640       return false;
641     }
642     String p1 = path1.replace('\\', '/');
643     String p2 = path2.replace('\\', '/');
644     return p1.equals(p2);
645   }
646 }