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