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