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