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