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