2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import java.awt.Component;
24 import java.awt.Dimension;
25 import java.awt.GraphicsEnvironment;
27 import java.awt.Toolkit;
28 import java.awt.event.KeyEvent;
29 import java.awt.event.MouseEvent;
30 import java.io.BufferedReader;
32 import java.io.FileOutputStream;
33 import java.io.FileReader;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.io.Reader;
38 import java.lang.reflect.Method;
40 import java.nio.channels.Channels;
41 import java.nio.channels.ReadableByteChannel;
42 import java.nio.file.Files;
43 import java.nio.file.Path;
44 import java.nio.file.Paths;
45 import java.nio.file.StandardCopyOption;
46 import java.nio.file.attribute.BasicFileAttributes;
47 import java.util.Date;
48 import java.util.Locale;
50 import java.util.Objects;
51 import java.util.Properties;
52 import java.util.logging.ConsoleHandler;
53 import java.util.logging.Level;
54 import java.util.logging.Logger;
56 import javax.swing.SwingUtilities;
58 import org.json.simple.parser.JSONParser;
59 import org.json.simple.parser.ParseException;
61 import com.stevesoft.pat.Regex;
63 import jalview.bin.Jalview;
64 import jalview.javascript.json.JSON;
65 import swingjs.api.JSUtilI;
67 * System platform information used by Applet and Application
74 private static boolean isJS = /** @j2sNative true || */
77 private static Boolean isNoJSMac = null, isNoJSWin = null, isMac = null,
78 isWin = null, isLinux = null;
80 private static Boolean isHeadless = null;
82 private static swingjs.api.JSUtilI jsutil;
90 // this is ok - it's a highly embedded method in Java; the deprecation
92 // really a recommended best practice.
93 jsutil = ((JSUtilI) Class.forName("swingjs.JSUtil").newInstance());
94 } catch (InstantiationException | IllegalAccessException
95 | ClassNotFoundException e)
102 * added to group mouse events into Windows and nonWindows (mac, unix, linux)
106 public static boolean isMac()
108 return (isMac == null
109 ? (isMac = (System.getProperty("os.name").indexOf("Mac") >= 0))
113 public static int SHORTCUT_KEY_MASK = (Platform.isMac()
114 ? KeyEvent.META_DOWN_MASK
115 : KeyEvent.CTRL_DOWN_MASK);
119 if (!GraphicsEnvironment.isHeadless())
121 // Using non-deprecated Extended key mask modifiers, but Java 8 has no
122 // getMenuShortcutKeyMaskEx method
123 Toolkit tk = Toolkit.getDefaultToolkit();
124 Method method = null;
127 method = tk.getClass().getMethod("getMenuShortcutKeyMaskEx");
128 } catch (Exception e)
131 "Could not find Toolkit method getMenuShortcutKeyMaskEx. Trying getMenuShortcutKeyMask.");
137 method = tk.getClass().getMethod("getMenuShortcutKeyMask");
138 } catch (Exception e)
141 "Could not find Toolkit method getMenuShortcutKeyMaskEx or getMenuShortcutKeyMask.");
149 method.setAccessible(true);
150 SHORTCUT_KEY_MASK = ((int) method.invoke(tk, new Object[0]));
151 } catch (Exception e)
156 if (SHORTCUT_KEY_MASK <= 0xF)
158 // shift this into the extended region (was Java 8)
159 SHORTCUT_KEY_MASK = SHORTCUT_KEY_MASK << 6;
164 * added to group mouse events into Windows and nonWindows (mac, unix, linux)
168 public static boolean isWin()
170 return (isWin == null
171 ? (isWin = (System.getProperty("os.name").indexOf("Win") >= 0))
176 * added to check LaF for Linux
180 public static boolean isLinux()
182 return (isLinux == null
183 ? (isLinux = (System.getProperty("os.name")
184 .indexOf("Linux") >= 0))
190 * @return true if HTML5 JavaScript
192 public static boolean isJS()
198 * sorry folks - Macs really are different
200 * BH: disabled for SwingJS -- will need to check key-press issues
202 * @return true if we do things in a special way.
204 public static boolean isAMacAndNotJS()
206 return (isNoJSMac == null ? (isNoJSMac = !isJS && isMac()) : isNoJSMac);
210 * Check if we are on a Microsoft platform...
212 * @return true if we have to cope with another platform variation
214 public static boolean isWindowsAndNotJS()
216 return (isNoJSWin == null ? (isNoJSWin = !isJS && isWin()) : isNoJSWin);
221 * @return true if we are running in non-interactive no UI mode
223 public static boolean isHeadless()
225 if (isHeadless == null)
227 isHeadless = "true".equals(System.getProperty("java.awt.headless"));
233 * Construct the value that depends on the system architecture. The methods
234 * setting the value for subsequent platforms are chained after this call and
235 * finalized with a {@link PlatformDependentValue#value() value()} call.
238 * Platform.forArch(120).forMac(114).forWin(112).forLinux(115).value();
243 * @param defaultValue
244 * default value used if platform not determined
245 * @return platform dependent value wrapper object
247 public static <T> PlatformDependentValue<T> forArch(T defaultValue)
249 return new PlatformDependentValue<T>(defaultValue);
259 public static class PlatformDependentValue<T>
261 private T defaultValue = null;
263 private T macValue = null;
265 private T winValue = null;
267 private T linuxValue = null;
269 private T jsValue = null;
271 private T headlessValue = null;
273 private PlatformDependentValue(T value)
275 Objects.requireNonNull(value);
276 defaultValue = value;
280 * Set the value used on Mac platform.
286 public PlatformDependentValue<T> forMac(T value)
288 Objects.requireNonNull(value);
294 * Set the value used on Windows platform.
300 public PlatformDependentValue<T> forWin(T value)
302 Objects.requireNonNull(value);
308 * Set the value used on Linux platform.
314 public PlatformDependentValue<T> forLinux(T value)
316 Objects.requireNonNull(value);
322 * Set the value used on JS platform.
328 public PlatformDependentValue<T> forJS(T value)
330 Objects.requireNonNull(value);
336 * Set the value used on headless platform. The headless value takes
337 * precedence over other platforms if set.
343 public PlatformDependentValue<T> forHeadless(T value)
345 Objects.requireNonNull(value);
346 headlessValue = value;
351 * Get the value of the parameter respecting the platform. The headless
352 * platform takes precedence over any other platform if it has the value
355 * @return parameter value depending on the platform
359 if (headlessValue != null && isHeadless())
360 return headlessValue;
361 if (macValue != null && isMac())
363 if (winValue != null && isWin())
365 if (linuxValue != null && isLinux())
367 if (jsValue != null && isJS())
375 * @return nominal maximum command line length for this platform
377 public static int getMaxCommandLineLength()
379 // TODO: determine nominal limits for most platforms.
380 return 2046; // this is the max length for a windows NT system.
384 * Answers the input with every backslash replaced with a double backslash (an
385 * 'escaped' single backslash)
390 public static String escapeBackslashes(String s)
392 return s == null ? null : s.replace("\\", "\\\\");
396 * Answers true if the mouse event has Meta-down (Command key on Mac) or
397 * Ctrl-down (on other o/s). Note this answers _false_ if the Ctrl key is
398 * pressed instead of the Meta/Cmd key on Mac. To test for Ctrl-pressed on
399 * Mac, you can use e.isPopupTrigger().
404 public static boolean isControlDown(MouseEvent e)
406 return isControlDown(e, isMac());
410 * Overloaded version of method (to allow unit testing)
416 protected static boolean isControlDown(MouseEvent e, boolean aMac)
419 // System.out.println(e.isPopupTrigger()
420 // + " " + ((SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0)
421 // + " " + e.isControlDown());
423 ? !e.isPopupTrigger()
424 && (SHORTCUT_KEY_MASK & e.getModifiersEx()) != 0
425 : e.isControlDown());
428 // BH: I don't know about that previous method. Here is what SwingJS uses.
429 // Notice the distinction in mouse events. (BUTTON3_MASK == META)
431 // private static boolean isPopupTrigger(int id, int mods, boolean isWin) {
432 // boolean rt = ((mods & InputEvent.BUTTON3_MASK) != 0);
434 // if (id != MouseEvent.MOUSE_RELEASED)
437 //// // Oddly, Windows returns InputEvent.META_DOWN_MASK on release, though
438 //// // BUTTON3_DOWN_MASK for pressed. So here we just accept both.
440 //// actually, we can use XXX_MASK, not XXX_DOWN_MASK and avoid this issue,
442 //// J2S adds the appropriate extended (0x3FC0) and simple (0x3F) modifiers.
446 // // mac, linux, unix
447 // if (id != MouseEvent.MOUSE_PRESSED)
449 // boolean lt = ((mods & InputEvent.BUTTON1_MASK) != 0);
450 // boolean ctrl = ((mods & InputEvent.CTRL_MASK) != 0);
451 // return rt || (ctrl && lt);
457 * Windows (not Mac, Linux, or Unix) and right button to test for the
458 * right-mouse pressed event in Windows that would have opened a menu or a
464 public static boolean isWinRightButton(MouseEvent e)
466 // was !isAMac(), but that is true also for Linux and Unix and JS,
468 return isWin() && SwingUtilities.isRightMouseButton(e);
472 * Windows (not Mac, Linux, or Unix) and middle button -- for mouse wheeling
473 * without pressing the button.
478 public static boolean isWinMiddleButton(MouseEvent e)
480 // was !isAMac(), but that is true also for Linux and Unix and JS
481 return isWin() && SwingUtilities.isMiddleMouseButton(e);
484 public static boolean allowMnemonics()
489 public final static int TIME_RESET = 0;
491 public final static int TIME_MARK = 1;
493 public static final int TIME_SET = 2;
495 public static final int TIME_GET = 3;
497 public static long time, mark, set, duration;
502 * Platform.timeCheck(null, Platform.TIME_MARK);
506 * Platform.timeCheck("some message", Platform.TIME_MARK);
508 * reset...[set/mark]n...get
513 public static void timeCheck(String msg, int mode)
515 long t = System.currentTimeMillis();
523 System.err.println("Platform: timer reset\t\t\t" + msg);
529 // total time between set/mark points
530 duration += (t - set);
540 System.err.println("Platform: timer mark\t" + ((t - time) / 1000f)
541 + "\t" + ((t - mark) / 1000f) + "\t" + msg);
552 System.err.println("Platform: timer dur\t" + ((t - time) / 1000f)
553 + "\t" + ((duration) / 1000f) + "\t" + msg);
560 public static void cacheFileData(String path, Object data)
562 if (isJS && data != null)
564 jsutil.cachePathData(path, data);
568 public static void cacheFileData(File file)
572 byte[] data = Platform.getFileBytes(file);
576 cacheFileData(file.toString(), data);
582 public static byte[] getFileBytes(File f)
584 return (isJS && f != null ? jsutil.getBytes(f) : null);
587 public static byte[] getFileAsBytes(String fileStr)
589 if (isJS && fileStr != null)
591 byte[] bytes = (byte[]) jsutil.getFile(fileStr, false);
592 cacheFileData(fileStr, bytes);
598 public static String getFileAsString(String url)
600 if (isJS && url != null)
602 String ret = (String) jsutil.getFile(url, true);
603 cacheFileData(url, ret);
609 public static boolean setFileBytes(File f, String urlstring)
611 if (isJS && f != null && urlstring != null)
613 @SuppressWarnings("unused")
614 byte[] bytes = getFileAsBytes(urlstring);
615 jsutil.setFileBytes(f, bytes);
621 public static void addJ2SBinaryType(String ext)
625 jsutil.addBinaryFileType(ext);
630 * Encode the URI using JavaScript encodeURIComponent
633 * @return encoded value
635 public static String encodeURI(String value)
638 * @j2sNative value = encodeURIComponent(value);
644 * Open the URL using a simple window call if this is JavaScript
647 * @return true if window has been opened
649 public static boolean openURL(String url) throws IOException
664 public static String getUniqueAppletID()
666 return (isJS ? (String) jsutil.getAppletAttribute("_uniqueId") : null);
671 * Read the Info block for this applet.
676 * @return unique id for this applet
678 public static void readInfoProperties(String prefix, Properties p)
682 String id = getUniqueAppletID();
686 @SuppressWarnings("unused")
687 Object info = jsutil.getAppletAttribute("__Info");
689 * @j2sNative for (key in info) { value = info[key];
692 if (key.indexOf(prefix) == 0)
694 System.out.println("Platform id=" + id + " reading Info." + key
706 public static void setAjaxJSON(URL url)
714 public static Object parseJSON(InputStream response)
715 throws IOException, ParseException
719 return JSON.parse(response);
722 BufferedReader br = null;
725 br = new BufferedReader(new InputStreamReader(response, "UTF-8"));
726 return new JSONParser().parse(br);
734 } catch (IOException e)
742 public static Object parseJSON(String json) throws ParseException
744 return (isJS() ? JSON.parse(json) : new JSONParser().parse(json));
747 public static Object parseJSON(Reader r)
748 throws IOException, ParseException
757 return new JSONParser().parse(r);
759 // Using a file reader is not currently supported in SwingJS JavaScript
761 if (r instanceof FileReader)
763 throw new IOException(
764 "StringJS does not support FileReader parsing for JSON -- but it could...");
766 return JSON.parse(r);
771 * Dump the input stream to an output file.
775 * @throws IOException
776 * if the file cannot be created or there is a problem reading the
779 public static void streamToFile(InputStream is, File outFile)
784 jsutil.setFileBytes(outFile, is);
787 FileOutputStream fio = new FileOutputStream(outFile);
790 byte[] bb = new byte[32 * 1024];
792 while ((l = is.read(bb)) > 0)
803 * Add a known domain that implements access-control-allow-origin:*
805 * These should be reviewed periodically.
808 * for a service that is not allowing ajax
810 * @author hansonr@stolaf.edu
813 public static void addJ2SDirectDatabaseCall(String domain)
818 jsutil.addDirectDatabaseCall(domain);
820 "Platform adding known access-control-allow-origin * for domain "
825 * J2S.addDirectDatabaseCall(domain);
832 * Allow for URL-line command arguments. Untested.
835 public static void getURLCommandArguments()
839 * Retrieve the first query field as command arguments to Jalview. Include
840 * only if prior to "?j2s" or "&j2s" or "#". Assign the applet's
841 * __Info.args element to this value.
844 * decodeURI((document.location.href.replace("&","?").split("?j2s")[0]
845 * + "?").split("?")[1].split("#")[0]); a && (System.out.println("URL arguments detected were "+a)) &&
846 * (J2S.thisApplet.__Info.urlargs = a.split(" "));
847 * (!J2S.thisApplet.__Info.args || J2S.thisApplet.__Info.args == "" || J2S.thisApplet.__Info.args == "??") && (J2S.thisApplet.__Info.args = a) && (System.out.println("URL arguments were passed to J2S main."));
849 } catch (Throwable t)
855 * A (case sensitive) file path comparator that ignores the difference between
862 public static boolean pathEquals(String path1, String path2)
866 return path2 == null;
872 String p1 = path1.replace('\\', '/');
873 String p2 = path2.replace('\\', '/');
874 return p1.equals(p2);
876 ///////////// JAL-3253 Applet additions //////////////
879 * Retrieve the object's embedded size from a div's style on a page if
880 * embedded in SwingJS.
883 * JFrame or JInternalFrame
884 * @param defaultWidth
885 * use -1 to return null (no default size)
886 * @param defaultHeight
887 * @return the embedded dimensions or null (no default size or not embedded)
889 public static Dimension getDimIfEmbedded(Component frame,
890 int defaultWidth, int defaultHeight)
895 d = (Dimension) getEmbeddedAttribute(frame, "dim");
897 return (d == null && defaultWidth >= 0
898 ? new Dimension(defaultWidth, defaultHeight)
903 public static Regex newRegex(String regex)
905 return newRegex(regex, null);
908 public static Regex newRegex(String searchString, String replaceString)
911 return (replaceString == null ? new Regex(searchString)
912 : new Regex(searchString, replaceString));
915 public static Regex newRegexPerl(String code)
918 return Regex.perlCode(code);
922 * Initialize Java debug logging. A representative sample -- adapt as desired.
924 public static void startJavaLogging()
930 logClass("java.awt.EventDispatchThread", "java.awt.EventQueue",
931 "java.awt.Component", "java.awt.focus.Component",
932 "java.awt.event.Component",
933 "java.awt.focus.DefaultKeyboardFocusManager");
938 * Initiate Java logging for a given class. Only for Java, not JavaScript;
939 * Allows debugging of complex event processing.
943 public static void logClass(String... classNames)
951 Logger rootLogger = Logger.getLogger("");
952 rootLogger.setLevel(Level.ALL);
953 ConsoleHandler consoleHandler = new ConsoleHandler();
954 consoleHandler.setLevel(Level.ALL);
955 for (int i = classNames.length; --i >= 0;)
957 Logger logger = Logger.getLogger(classNames[i]);
958 logger.setLevel(Level.ALL);
959 logger.addHandler(consoleHandler);
965 * load a resource -- probably a core file -- if and only if a particular
966 * class has not been instantialized. We use a String here because if we used
967 * a .class object, that reference itself would simply load the class, and we
968 * want the core package to include that as well.
970 * @param resourcePath
973 public static void loadStaticResource(String resourcePath,
978 jsutil.loadResourceIfClassUnknown(resourcePath, className);
982 public static void ensureRegex()
986 loadStaticResource("core/core_stevesoft.z.js",
987 "com.stevesoft.pat.Regex");
992 * Set the "app" property of the HTML5 applet object, for example,
993 * "testApplet.app", to point to the Jalview instance. This will be the object
994 * that page developers use that is similar to the original Java applet object
995 * that was accessed via LiveConnect.
999 public static void setAppClass(Object j)
1003 jsutil.setAppClass(j);
1009 * If this frame is embedded in a web page, return a known type.
1012 * a JFrame or JInternalFrame
1014 * "name", "node", "init", "dim", or any DOM attribute, such as "id"
1015 * @return null if frame is not embedded.
1017 public static Object getEmbeddedAttribute(Component frame, String type)
1019 return (isJS ? jsutil.getEmbeddedAttribute(frame, type) : null);
1022 public static void stackTrace()
1026 throw new NullPointerException();
1027 } catch (Exception e)
1029 e.printStackTrace();
1034 public static URL getDocumentBase()
1036 return (isJS ? jsutil.getDocumentBase() : null);
1039 public static URL getCodeBase()
1041 return (isJS ? jsutil.getCodeBase() : null);
1044 public static String getUserPath(String subpath)
1046 char sep = File.separatorChar;
1047 return System.getProperty("user.home") + sep
1048 + subpath.replace('/', sep);
1052 * This method enables checking if a cached file has exceeded a certain
1053 * threshold(in days)
1058 * the threshold in days
1061 public static boolean isFileOlderThanThreshold(File file, int noOfDays)
1065 // not meaningful in SwingJS -- this is a session-specific temp file. It
1066 // doesn't have a timestamp.
1069 Path filePath = file.toPath();
1070 BasicFileAttributes attr;
1074 attr = Files.readAttributes(filePath, BasicFileAttributes.class);
1075 diffInDays = (int) ((new Date().getTime()
1076 - attr.lastModifiedTime().toMillis())
1077 / (1000 * 60 * 60 * 24));
1078 // System.out.println("Diff in days : " + diffInDays);
1079 } catch (IOException e)
1081 e.printStackTrace();
1083 return noOfDays <= diffInDays;
1087 * Get the leading integer part of a string that begins with an integer.
1090 * - the string input to process
1092 * - value returned if unsuccessful
1095 public static int getLeadingIntegerValue(String input, int failValue)
1103 int val = /** @j2sNative 1 ? parseInt(input) : */
1105 return (val == val + 0 ? val : failValue);
1107 // JavaScript does not support Regex ? lookahead
1108 String[] parts = input.split("(?=\\D)(?<=\\d)");
1109 if (parts != null && parts.length > 0 && parts[0].matches("[0-9]+"))
1111 return Integer.valueOf(parts[0]);
1116 public static Map<String, Object> getAppletInfoAsMap()
1118 return (isJS ? jsutil.getAppletInfoAsMap() : null);
1122 * Get the SwingJS applet ID and combine that with the frameType
1125 * "alignment", "desktop", etc., or null
1128 public static String getAppID(String frameType)
1131 String id = Jalview.getInstance().j2sAppletID;
1134 Jalview.getInstance().j2sAppletID = id = (isJS
1135 ? (String) jsutil.getAppletAttribute("_id")
1138 return id + (frameType == null ? "" : "-" + frameType);
1142 * Option to avoid unnecessary seeking of nonexistent resources in JavaScript.
1143 * Works in Java as well.
1148 public static Locale getLocaleOrNone(Locale loc)
1150 return (isJS && loc.getLanguage() == "en" ? new Locale("") : loc);
1154 * From UrlDownloadClient; trivial in JavaScript; painful in Java.
1158 * @throws IOException
1160 public static void download(String urlstring, String outfile)
1164 try (InputStream is = new URL(urlstring).openStream())
1167 { // so much easier!
1168 streamToFile(is, new File(outfile));
1171 temp = Files.createTempFile(".jalview_", ".tmp");
1172 try (FileOutputStream fos = new FileOutputStream(temp.toString());
1173 ReadableByteChannel rbc = Channels.newChannel(is))
1175 fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
1176 // copy tempfile to outfile once our download completes
1177 // incase something goes wrong
1178 Files.copy(temp, Paths.get(outfile),
1179 StandardCopyOption.REPLACE_EXISTING);
1181 } catch (IOException e)
1190 Files.deleteIfExists(temp);
1192 } catch (IOException e)
1194 System.out.println("Exception while deleting download temp file: "