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