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