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