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