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