96940e5352d80c67dbd6435afe0c282a21ace5b3
[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     return /** @j2sNative f && f._bytes || */
336     null;
337   }
338
339   public static byte[] getFileAsBytes(String fileStr)
340   {
341     byte[] bytes = null;
342     // BH 2018 hack for no support for access-origin
343     /**
344      * @j2sNative bytes = swingjs.JSUtil.getFileAsBytes$O(fileStr)
345      */
346     cacheFileData(fileStr, bytes);
347     return bytes;
348   }
349
350   @SuppressWarnings("unused")
351   public static String getFileAsString(String url)
352   {
353     String ret = null;
354     /**
355      * @j2sNative
356      * 
357      *            ret = swingjs.JSUtil.getFileAsString$S(url);
358      * 
359      * 
360      */
361     cacheFileData(url, ret);
362     return ret;
363   }
364
365   public static boolean setFileBytes(File f, String urlstring)
366   {
367     if (!isJS())
368     {
369       return false;
370     }
371     @SuppressWarnings("unused")
372     byte[] bytes = getFileAsBytes(urlstring);
373     /**
374      * @j2sNative f._bytes = bytes;
375      */
376     return true;
377   }
378
379   public static void addJ2SBinaryType(String ext)
380   {
381     /**
382      * @j2sNative
383      * 
384      *            J2S._binaryTypes.push("." + ext + "?");
385      * 
386      */
387   }
388
389   /**
390    * Encode the URI using JavaScript encodeURIComponent
391    * 
392    * @param value
393    * @return encoded value
394    */
395   public static String encodeURI(String value)
396   {
397     /**
398      * @j2sNative value = encodeURIComponent(value);
399      */
400     return value;
401   }
402
403   /**
404    * Open the URL using a simple window call if this is JavaScript
405    * 
406    * @param url
407    * @return true if window has been opened
408    */
409   public static boolean openURL(String url) throws IOException
410   {
411     if (!isJS())
412     {
413       BrowserLauncher.openURL(url);
414       return false;
415     }
416     /**
417      * @j2sNative
418      * 
419      * 
420      *            window.open(url);
421      */
422     return true;
423   }
424
425   public static String getUniqueAppletID()
426   {
427     @SuppressWarnings("unused")
428     ThreadGroup g = Thread.currentThread().getThreadGroup();
429     /**
430      * @j2sNative return g.html5Applet._uniqueId;
431      *
432      */
433     return null;
434
435   }
436
437   /**
438    * Read the Info block for this applet.
439    * 
440    * @param prefix
441    *          "jalview_"
442    * @param p
443    * @return unique id for this applet
444    */
445   public static void readInfoProperties(String prefix, Properties p)
446   {
447     if (!isJS())
448     {
449       return;
450     }
451     @SuppressWarnings("unused")
452     ThreadGroup g = Thread.currentThread().getThreadGroup();
453     String id = getUniqueAppletID();
454     String key = "", value = "";
455     /**
456      * @j2sNative var info = g.html5Applet.__Info || {}; for (var key in info) {
457      *            if (key.indexOf(prefix) == 0) { value = "" + info[key];
458      */
459
460     System.out.println(
461             "Platform id=" + id + " reading Info." + key + " = " + value);
462     p.put(key, value);
463
464     /**
465      * @j2sNative
466      * 
467      * 
468      *            } }
469      */
470   }
471
472   public static void setAjaxJSON(URL url)
473   {
474     if (isJS())
475     {
476       JSON.setAjax(url);
477     }
478   }
479
480   public static Object parseJSON(InputStream response)
481           throws IOException, ParseException
482   {
483     if (isJS())
484     {
485       return JSON.parse(response);
486     }
487
488     BufferedReader br = null;
489     try
490     {
491       br = new BufferedReader(new InputStreamReader(response, "UTF-8"));
492       return new JSONParser().parse(br);
493     } finally
494     {
495       if (br != null)
496       {
497         try
498         {
499           br.close();
500         } catch (IOException e)
501         {
502           // ignore
503         }
504       }
505     }
506   }
507
508   public static Object parseJSON(String json) throws ParseException
509   {
510     return (isJS() ? JSON.parse(json)
511             : new JSONParser().parse(json));
512   }
513
514   public static Object parseJSON(Reader r)
515           throws IOException, ParseException
516   {
517     if (r == null)
518     {
519       return null;
520     }
521
522     if (!isJS())
523     {
524       return new JSONParser().parse(r);
525     }
526     // Using a file reader is not currently supported in SwingJS JavaScript
527
528     if (r instanceof FileReader)
529     {
530       throw new IOException(
531               "StringJS does not support FileReader parsing for JSON -- but it could...");
532     }
533     return JSON.parse(r);
534
535   }
536
537   /**
538    * Dump the input stream to an output file.
539    * 
540    * @param is
541    * @param outFile
542    * @throws IOException
543    *           if the file cannot be created or there is a problem reading the
544    *           input stream.
545    */
546   public static void streamToFile(InputStream is, File outFile)
547           throws IOException
548   {
549     FileOutputStream fio = new FileOutputStream(outFile);
550     try
551     {
552       if (isJS()
553               && /**
554                   * @j2sNative outFile.setBytes$O && outFile.setBytes$O(is) &&
555                   */
556               true)
557       {
558         return;
559       }
560       byte[] bb = new byte[32 * 1024];
561       int l;
562       while ((l = is.read(bb)) > 0)
563       {
564         fio.write(bb, 0, l);
565       }
566     } finally
567     {
568       fio.close();
569     }
570   }
571
572   /**
573    * Add a known domain that implements access-control-allow-origin:*
574    * 
575    * These should be reviewed periodically.
576    * 
577    * @param domain
578    *          for a service that is not allowing ajax
579    * 
580    * @author hansonr@stolaf.edu
581    * 
582    */
583   public static void addJ2SDirectDatabaseCall(String domain)
584   {
585
586     if (isJS())
587     {
588       System.out.println(
589             "Platform adding known access-control-allow-origin * for domain "
590                     + domain);
591       /**
592        * @j2sNative
593        * 
594        *            J2S.addDirectDatabaseCall(domain);
595        */
596     }
597
598   }
599
600   public static void getURLCommandArguments()
601   {
602
603     /**
604      * Retrieve the first query field as command arguments to Jalview. Include
605      * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's __Info.args
606      * element to this value.
607      * 
608      * @j2sNative var a =
609      *            decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
610      *            + "?").split("?")[1].split("#")[0]); a &&
611      *            (J2S.thisApplet.__Info.args = a.split(" "));
612      */
613
614   }
615
616   public static URL getDocumentBase()
617   {
618     try
619     {
620       return (isJS() ? new URL(/**
621                                 * @j2sNative J2S.thisApplet._applet.appletViewer.appletDocumentBase
622                                 *            ||
623                                 */
624               "") : null);
625     } catch (MalformedURLException e)
626     {
627       return null;
628     }
629   }
630
631   public static URL getCodeBase()
632   {
633     try
634     {
635       return (isJS() ? new URL(/**
636                                 * @j2sNative J2S.thisApplet._applet.appletViewer.appletCodeBase
637                                 *            ||
638                                 */
639               "") : null);
640     } catch (MalformedURLException e)
641     {
642       return null;
643     }
644   }
645
646 }