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