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