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