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