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