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