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