From 726bda0e9aea2d68b90803e142346cc46c8d4772 Mon Sep 17 00:00:00 2001 From: Ben Soares Date: Fri, 19 Mar 2021 22:53:23 +0000 Subject: [PATCH] JAL-3503 Added a Startup tab in Preferences, with gui for JVMMEMMAX and JVMMEMPC settings. Allowed jalview.bin.Launcher and getdown to read/use these memory settings at launch. Added channel.props to getdown appdir, used to determing correct .jalview_properties file. Refactored MemorySetting a bit, with more helper methods for the Preferences tab. --- build.gradle | 7 + getdown/src/getdown/ant/pom.xml | 2 +- getdown/src/getdown/core/pom.xml | 2 +- .../com/threerings/getdown/data/Application.java | 23 +- .../src/main/java/jalview/bin/LaunchUtils.java | 41 ++ .../src/main/java/jalview/bin/MemorySetting.java | 249 ++++++--- .../main/java/jalview/util/ChannelProperties.java | 277 +++++++++ .../src/main/java/jalview/util/StringUtils.java | 585 ++++++++++++++++++++ getdown/src/getdown/launcher/pom.xml | 2 +- getdown/src/getdown/mvn_cmd | 2 +- getdown/src/getdown/pom.xml | 2 +- gradle.properties | 1 + resources/lang/Messages.properties | 11 + resources/lang/Messages_es.properties | 11 + src/jalview/bin/LaunchUtils.java | 41 ++ src/jalview/bin/Launcher.java | 18 +- src/jalview/bin/MemorySetting.java | 249 ++++++--- src/jalview/gui/Preferences.java | 21 + src/jalview/jbgui/GPreferences.java | 322 +++++++++++ src/jalview/util/ChannelProperties.java | 30 +- src/jalview/util/StringUtils.java | 20 +- 21 files changed, 1757 insertions(+), 159 deletions(-) create mode 100644 getdown/src/getdown/core/src/main/java/jalview/bin/LaunchUtils.java create mode 100644 getdown/src/getdown/core/src/main/java/jalview/util/ChannelProperties.java create mode 100644 getdown/src/getdown/core/src/main/java/jalview/util/StringUtils.java create mode 100644 src/jalview/bin/LaunchUtils.java diff --git a/build.gradle b/build.gradle index e2a5605..6dac841 100644 --- a/build.gradle +++ b/build.gradle @@ -102,6 +102,7 @@ ext { // Import channel_properties channelDir = string("${jalviewDir}/${channel_properties_dir}/${propertiesChannelName}") channelGradleProperties = string("${channelDir}/channel_gradle.properties") + channelPropsFile = string("${channelDir}/${resource_dir}/${channel_props}") overrideProperties(channelGradleProperties, false) // local build environment properties // can be "projectDir/local.properties" @@ -1505,6 +1506,12 @@ task getdownWebsite() { } getdownWebsiteResourceFilenames += "${getdownAppDistDir}/${getdown_build_properties}" + copy { + from channelPropsFile + into getdownWebsiteDir + } + getdownWebsiteResourceFilenames += file(channelPropsFile).getName() + // set some getdown_txt_ properties then go through all properties looking for getdown_txt_... def props = project.properties.sort { it.key } if (getdownAltJavaMinVersion != null && getdownAltJavaMinVersion.length() > 0) { diff --git a/getdown/src/getdown/ant/pom.xml b/getdown/src/getdown/ant/pom.xml index 9b26d50..e67984c 100644 --- a/getdown/src/getdown/ant/pom.xml +++ b/getdown/src/getdown/ant/pom.xml @@ -4,7 +4,7 @@ com.threerings.getdown getdown - 1.8.3-1.2.10_FJVL + 1.8.3-1.2.11_FJVL getdown-ant diff --git a/getdown/src/getdown/core/pom.xml b/getdown/src/getdown/core/pom.xml index eb6f388..f909444 100644 --- a/getdown/src/getdown/core/pom.xml +++ b/getdown/src/getdown/core/pom.xml @@ -4,7 +4,7 @@ com.threerings.getdown getdown - 1.8.3-1.2.10_FJVL + 1.8.3-1.2.11_FJVL getdown-core diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java index 2022750..468796c 100644 --- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java @@ -31,7 +31,8 @@ import java.util.zip.GZIPInputStream; import jalview.bin.HiDPISetting; import jalview.bin.MemorySetting; -//import com.install4j.api.launcher.Variables; +import jalview.bin.LaunchUtils; +import jalview.util.ChannelProperties; import com.threerings.getdown.util.*; // avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+ @@ -1181,6 +1182,26 @@ public class Application continue; } } + + // use saved preferences if no cmdline args + File channelProps = getLocalPath(ChannelProperties.CHANNEL_PROPERTIES_FILENAME); + if (channelProps.exists()) { + try { + InputStream is = new FileInputStream(channelProps); + ChannelProperties.loadProps(is); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + boolean useCustomisedSettings = Boolean.parseBoolean(LaunchUtils.getUserPreference(MemorySetting.CUSTOMISED_SETTINGS)); + if (useCustomisedSettings) { + if (jvmmempc == null) { + jvmmempc = LaunchUtils.getUserPreference(MemorySetting.MEMORY_JVMMEMPC); + } + if (jvmmemmax == null) { + jvmmemmax = LaunchUtils.getUserPreference(MemorySetting.MEMORY_JVMMEMMAX); + } + } // add the memory setting from jvmmempc and jvmmemmax long maxMemLong = -1; diff --git a/getdown/src/getdown/core/src/main/java/jalview/bin/LaunchUtils.java b/getdown/src/getdown/core/src/main/java/jalview/bin/LaunchUtils.java new file mode 100644 index 0000000..89d6069 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/jalview/bin/LaunchUtils.java @@ -0,0 +1,41 @@ +package jalview.bin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; + +import jalview.util.ChannelProperties; + +public class LaunchUtils +{ + + private static Properties userPreferences = null; + + public static String getUserPreference(String key) + { + if (userPreferences == null) + { + String channelPrefsFilename = ChannelProperties + .getProperty("preferences.filename"); + String propertiesFile = System.getProperty("user.home") + + File.separatorChar + channelPrefsFilename; + try + { + userPreferences = new Properties(); + userPreferences.load(new FileInputStream(propertiesFile)); + } catch (FileNotFoundException e) + { + // didn't find user preferences file + return null; + } catch (IOException e) + { + System.err.println(e.getMessage()); + return null; + } + } + return userPreferences.getProperty(key); + } + +} diff --git a/getdown/src/getdown/core/src/main/java/jalview/bin/MemorySetting.java b/getdown/src/getdown/core/src/main/java/jalview/bin/MemorySetting.java index 5d7f14c..55e304d 100644 --- a/getdown/src/getdown/core/src/main/java/jalview/bin/MemorySetting.java +++ b/getdown/src/getdown/core/src/main/java/jalview/bin/MemorySetting.java @@ -1,4 +1,6 @@ /* + + private static String ADJUSTMENT_MESSAGE = null; * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * @@ -31,6 +33,7 @@ package jalview.bin; * @author bsoares * */ + public class MemorySetting { public static final String MAX_HEAPSIZE_PERCENT_PROPERTY_NAME = "jvmmempc"; @@ -49,13 +52,33 @@ public class MemorySetting private static final long NOMEM_MAX_HEAPSIZE_GB_DEFAULT = 8; + public static final String NS = "MEMORY"; + + public static final String CUSTOMISED_SETTINGS = NS + + "_CUSTOMISED_SETTINGS"; + + public static final String MEMORY_JVMMEMPC = NS + "_" + + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.toUpperCase(); + + public static final String MEMORY_JVMMEMMAX = NS + "_" + + MAX_HEAPSIZE_PROPERTY_NAME.toUpperCase(); + protected static boolean logToClassChecked = false; + public static String memorySuffixes = "bkmgt"; // order of the suffixes is + // important! + public static long getMemorySetting() { return getMemorySetting(null, null); } + public static long getMemorySetting(String jvmmemmaxarg, + String jvmmempcarg) + { + return getMemorySetting(jvmmemmaxarg, jvmmempcarg, true, false); + } + /** * Decide on appropriate memory setting for Jalview based on the two arguments * values: jvmmempc - the maximum percentage of total physical memory to @@ -83,99 +106,66 @@ public class MemorySetting * @param jvmmempcarg * Max percentage of physical memory to use. Defaults to "90". * + * @param useProps + * boolean to decide whether to look at System properties. + * * @return The amount of memory (in bytes) to allocate to Jalview */ public static long getMemorySetting(String jvmmemmaxarg, - String jvmmempcarg) + String jvmmempcarg, boolean useProps, boolean quiet) { // actual Xmx value-to-be long maxMemLong = -1; + clearAdjustmentMessage(); // (absolute) jvmmaxmem setting, start with default long memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; - if (jvmmemmaxarg == null) + if (jvmmemmaxarg == null && useProps) { jvmmemmaxarg = System.getProperty(MAX_HEAPSIZE_PROPERTY_NAME); } String jvmmemmax = jvmmemmaxarg; if (jvmmemmax != null && jvmmemmax.length() > 0) { - long multiplier = 1; - switch (jvmmemmax.toLowerCase().substring(jvmmemmax.length() - 1)) - { - case "t": - multiplier = 1099511627776L; // 2^40 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "g": - multiplier = 1073741824; // 2^30 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "m": - multiplier = 1048576; // 2^20 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "k": - multiplier = 1024; // 2^10 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "b": - multiplier = 1; // 2^0 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - default: - break; - } - // parse the arg try { - memmax = Long.parseLong(jvmmemmax); + memmax = memoryStringToLong(jvmmemmax); + if (memmax == 0) + { + throw (new NumberFormatException("Not allowing 0")); + } } catch (NumberFormatException e) { memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg - + "') badly formatted, using default (" - + MAX_HEAPSIZE_GB_DEFAULT + "g)."); - } - - // apply multiplier if not too big (i.e. bigger than a long) - if (Long.MAX_VALUE / memmax < multiplier) - { - memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; - System.out.println("MemorySetting Property '" - + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg - + ") too big, using default (" + MAX_HEAPSIZE_GB_DEFAULT - + "g)."); - } - else - { - memmax = multiplier * memmax; + + "') badly formatted or 0, using default (" + + MAX_HEAPSIZE_GB_DEFAULT + "g).", quiet); } // check at least minimum value (this accounts for negatives too) if (memmax < APPLICATION_MIN_MEMORY) { memmax = APPLICATION_MIN_MEMORY; - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg + ") too small, using minimum (" + APPLICATION_MIN_MEMORY - + ")."); + + ").", quiet); } } else { // no need to warn if no setting - // System.out.println("MemorySetting Property '" + maxHeapSizeProperty + // adjustmentMessage("MemorySetting Property '" + maxHeapSizeProperty // + "' not // set."); } // get max percent of physical memory, starting with default float percent = MAX_HEAPSIZE_PERCENT_DEFAULT; - if (jvmmempcarg == null) + if (jvmmempcarg == null && useProps) { jvmmempcarg = System.getProperty(MAX_HEAPSIZE_PERCENT_PROPERTY_NAME); } @@ -185,24 +175,24 @@ public class MemorySetting { if (jvmmempc != null) { - float trypercent = Float.parseFloat(jvmmempc); - if (0 < trypercent && trypercent <= 100f) + int trypercent = Integer.parseInt(jvmmempc); + if (0 <= trypercent && trypercent <= 100) { percent = trypercent; } else { - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME - + "' should be in range 1..100. Using default " + percent - + "%"); + + "' should be in range 0..100. Using default " + percent + + "%", quiet); } } } catch (NumberFormatException e) { - System.out.println("MemorySetting property '" + setAdjustmentMessage("MemorySetting property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg - + ") badly formatted"); + + ") badly formatted", quiet); } // catch everything in case of no com.sun.management.OperatingSystemMXBean @@ -223,10 +213,10 @@ public class MemorySetting { mempc = physicalMem - LEAVE_FREE_MIN_MEMORY; reducedmempc = true; - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg + ") too large. Leaving free space for OS and reducing to (" - + mempc + ")."); + + mempc + ").", quiet); } // check for minimum application memsize @@ -234,16 +224,16 @@ public class MemorySetting { if (reducedmempc) { - System.out.println("Reduced MemorySetting (" + mempc + setAdjustmentMessage("Reduced MemorySetting (" + mempc + ") too small. Increasing to application minimum (" - + APPLICATION_MIN_MEMORY + ")."); + + APPLICATION_MIN_MEMORY + ").", quiet); } else { - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg + ") too small. Using minimum (" - + APPLICATION_MIN_MEMORY + ")."); + + APPLICATION_MIN_MEMORY + ").", quiet); } mempc = APPLICATION_MIN_MEMORY; } @@ -252,19 +242,21 @@ public class MemorySetting { // not enough memory for application, just try and grab what we can! mempc = physicalMem; - System.out.println( + setAdjustmentMessage( "Not enough physical memory for application. Ignoring MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg + "). Using maximum memory available (" - + physicalMem + ")."); + + physicalMem + ").", + quiet); } } catch (Throwable t) { memoryPercentError = true; - System.out.println( - "Problem calling GetMemory.getPhysicalMemory(). Likely to be problem with com.sun.management.OperatingSystemMXBean"); + setAdjustmentMessage( + "Problem calling GetMemory.getPhysicalMemory(). Likely to be problem with com.sun.management.OperatingSystemMXBean", + quiet); t.printStackTrace(); } @@ -281,9 +273,10 @@ public class MemorySetting // == null)) && memmax > NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE) { - System.out.println( + setAdjustmentMessage( "Capping maximum memory to " + NOMEM_MAX_HEAPSIZE_GB_DEFAULT - + "g due to failure to read physical memory size."); + + "g due to failure to read physical memory size.", + quiet); memmax = NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; } @@ -299,4 +292,120 @@ public class MemorySetting return maxMemLong; } + public static boolean isValidMemoryString(String text) + { + if (text.length() > 0) + { + char lastChar = text.charAt(text.length() - 1); + char[] otherChars = text.substring(0, text.length() - 1) + .toCharArray(); + for (char c : otherChars) + { + if (c < '0' || c > '9') + { + return false; + } + } + if ((lastChar < '0' || lastChar > '9') && memorySuffixes + .indexOf(Character.toLowerCase(lastChar)) == -1) + { + return false; + } + } + return true; + } + + public static long memoryStringToLong(String memString) + throws NumberFormatException + { + if (!isValidMemoryString(memString)) // not valid + { + throw (new NumberFormatException("Not a valid memory string")); + } + char suffix = Character + .toLowerCase(memString.charAt(memString.length() - 1)); + if ('0' <= suffix && suffix <= '9') // no suffix + { + return Long.valueOf(memString); + } + if (memorySuffixes.indexOf(suffix) == -1) // suffix is unknown + { + return -1; + } + + long multiplier = (long) Math.pow(2, + memorySuffixes.indexOf(suffix) * 10); // note order of suffixes in + // memorySuffixes important + // here! + // parse the arg. NumberFormatExceptions passed on to calling method + long mem = Long + .parseLong(memString.substring(0, memString.length() - 1)); + if (mem == 0) + { + return 0; + } + + // apply multiplier only if result is not too big (i.e. bigger than a long) + if (Long.MAX_VALUE / mem > multiplier) + { + return multiplier * mem; + } + else + { + // number too big for a Long. Limit to Long.MAX_VALUE + System.out.println("Memory parsing of '" + memString + + "' produces number too big. Limiting to Long.MAX_VALUE=" + + Long.MAX_VALUE); + return Long.MAX_VALUE; + } + } + + public static String memoryLongToString(long mem) + { + return memoryLongToString(mem, "%.1f"); + } + + public static String memoryLongToString(long mem, String format) + { + int exponent = 0; + float num = mem; + char suffix = 'b'; + + for (int i = 0; i < memorySuffixes.length(); i++) + { + char s = Character.toUpperCase(memorySuffixes.charAt(i)); + if (mem < (long) Math.pow(2, exponent + 10) + || i == memorySuffixes.length() - 1) // last suffix + { + suffix = s; + num = (float) (mem / Math.pow(2, exponent)); + break; + } + exponent += 10; + } + + return String.format(format, num) + suffix; + } + + private static String ADJUSTMENT_MESSAGE = null; + + private static void setAdjustmentMessage(String reason, boolean quiet) + { + ADJUSTMENT_MESSAGE = reason; + if (!quiet) + { + System.out.println(reason); + } + } + + public static void clearAdjustmentMessage() + { + ADJUSTMENT_MESSAGE = null; + } + + public static String getAdjustmentMessage() + { + return ADJUSTMENT_MESSAGE; + } + } \ No newline at end of file diff --git a/getdown/src/getdown/core/src/main/java/jalview/util/ChannelProperties.java b/getdown/src/getdown/core/src/main/java/jalview/util/ChannelProperties.java new file mode 100644 index 0000000..cf3b190 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/jalview/util/ChannelProperties.java @@ -0,0 +1,277 @@ +package jalview.util; + +import java.awt.Image; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.swing.ImageIcon; + +public class ChannelProperties +{ + + public static final String CHANNEL_PROPERTIES_FILENAME = "channel.props"; + + private static Properties channelProps; + + private static final Properties defaultProps; + + private static Map imageMap = new HashMap(); + + private static Map urlMap = new HashMap(); + + private static final ArrayList iconList; + + static + { + defaultProps = new Properties(); + // these should be kept up to date, but in real life they should never + // actually be used anyway. + defaultProps.put("app_name", "Jalview"); + defaultProps.put("banner", "/default_images/jalview_banner.png"); + defaultProps.put("logo.16", "/default_images/jalview_logo-16.png"); + defaultProps.put("logo.32", "/default_images/jalview_logo-32.png"); + defaultProps.put("logo.38", "/default_images/jalview_logo-38.png"); + defaultProps.put("logo.48", "/default_images/jalview_logo-48.png"); + defaultProps.put("logo.64", "/default_images/jalview_logo-64.png"); + defaultProps.put("logo.128", "/default_images/jalview_logo-128.png"); + defaultProps.put("logo.256", "/default_images/jalview_logo-256.png"); + defaultProps.put("logo.512", "/default_images/jalview_logo-512.png"); + defaultProps.put("rotatable_logo.48", + "/default_images/rotatable_jalview_logo-38.png"); + defaultProps.put("bg_logo.28", "/default_images/barton_group-28.png"); + defaultProps.put("bg_logo.30", "/default_images/barton_group-30.png"); + defaultProps.put("bg_logo.32", "/default_images/barton_group-32.png"); + defaultProps.put("uod_banner.28", "/default_images/UoD_banner-28.png"); + defaultProps.put("uod_banner.30", "/default_images/UoD_banner-30.png"); + defaultProps.put("uod_banner.32", "/default_images/UoD_banner-32.png"); + defaultProps.put("default_appbase", + "https://www.jalview.org/getdown/release/1.8"); + defaultProps.put("preferences.filename", ".jalview_properties"); + + // load channel_properties + Properties tryChannelProps = new Properties(); + URL channelPropsURL = ChannelProperties.class + .getResource("/" + CHANNEL_PROPERTIES_FILENAME); + if (channelPropsURL == null) + { + // complete failure of channel_properties, set all properties to defaults + System.err.println("Failed to find '/" + CHANNEL_PROPERTIES_FILENAME + + "' file at '" + + (channelPropsURL == null ? "null" + : channelPropsURL.toString()) + + "'. Using class defaultProps."); + tryChannelProps = defaultProps; + } + else + { + try + { + InputStream channelPropsIS = channelPropsURL.openStream(); + tryChannelProps.load(channelPropsIS); + channelPropsIS.close(); + } catch (IOException e) + { + System.err.println(e.getMessage()); + // return false; + } + } + channelProps = tryChannelProps; + + /* + * The following slight palava for caching an icon list is so that all sizes of icons + * are the same. i.e. if there are /any/ channel_properties icons to use, then _only_ + * use those channel_properties icons, don't mix in class default icons for missing + * sizes. If there are _no_ (usable) channel icons then we can use the class default icons. + */ + iconList = new ArrayList(); + List sizes = Arrays.asList("16", "32", "48", "64", "128", "256", + "512"); + for (String size : sizes) + { + Image logo = null; + // not using defaults or class props first time through + logo = ChannelProperties.getImage("logo." + size, null, false); + if (logo != null) + { + iconList.add(logo); + } + } + // now add the class defaults if there were no channel icons defined + if (iconList.size() == 0) + { + for (String size : sizes) + { + Image logo = null; + String path = defaultProps.getProperty("logo." + size); + URL imageURL = ChannelProperties.class.getResource(path); + ImageIcon imgIcon = imageURL == null ? null + : new ImageIcon(imageURL); + logo = imgIcon == null ? null : imgIcon.getImage(); + if (logo != null) + { + iconList.add(logo); + } + } + } + } + + public static void loadProps(InputStream is) + { + try + { + channelProps.load(is); + } catch (IOException e) + { + System.err.println(e.getMessage()); + } + } + + private static Properties channelProps() + { + return channelProps; + } + + private static Map imageMap() + { + return imageMap; + } + + private static Map urlMap() + { + return urlMap; + } + + /* + * getProperty(key) will get property value from channel_properties for key. + * If no property for key is found, it will fall back to using the defaultProps defined for this class. + */ + public static String getProperty(String key) + { + return getProperty(key, null, true); + } + + /* + * getProperty(key, defaultVal) will get property value from channel_properties for key. + * If no property for key is found, it will return defaultVal and NOT fall back to the class defaultProps. + */ + public static String getProperty(String key, String defaultVal) + { + return getProperty(key, defaultVal, false); + } + + /* + * internal method. note that setting useClassDefaultProps=true will ignore the provided defaultVal + */ + private static String getProperty(String key, String defaultVal, + boolean useClassDefaultProps) + { + if (channelProps() != null) + { + if (channelProps().containsKey(key)) + { + return channelProps().getProperty(key, + useClassDefaultProps ? defaultProps.getProperty(key) + : defaultVal); + } + else + { + System.err.println("Failed to get channel property '" + key + "'"); + } + } + return null; + } + + /* + * getImage(key) returns the channel defined image for property key. If that is null (e.g. due to + * no defined channel image or the image file being corrupt/unusable/missing) it uses the image + * defined in defaultChannelProps + */ + public static Image getImage(String key) + { + return getImage(key, null, true); + } + + /* + * getImage(key, defaultImg) will get image associated with value from channel_properties for key. + * If no property or associated image for key is found (or is usable), it will return defaultImg + * and NOT fall back to the class defaultProps. + */ + public static Image getImage(String key, Image defaultImg) + { + return getImage(key, defaultImg, false); + } + + /* + * internal method. note that setting useClassDefaultImage=true will ignore the provided defaultImg + */ + private static Image getImage(String key, Image defaultImg, + boolean useClassDefaultImage) + { + Image img = null; + if (imageMap().containsKey(key)) + { + img = imageMap().get(key); + } + // Catch a previously untried or failed load + if (img == null) + { + String path = getProperty(key, null, useClassDefaultImage); + if (path == null) // no channel property or class default property (if + // requested) + { + return useClassDefaultImage ? null : defaultImg; + } + + URL imageURL = ChannelProperties.class.getResource(path); + ImageIcon imgIcon = imageURL == null ? null : new ImageIcon(imageURL); + img = imgIcon == null ? null : imgIcon.getImage(); + if (img == null) + { + System.err.println( + "Failed to load channel image " + key + "=" + path); + if (!useClassDefaultImage) + { + return defaultImg; + } + } + else + { + imageMap().put(key, img); + urlMap.put(key, imageURL); + } + } + return img; + } + + /* + * Public method to get the URL object pointing to a cached image. + */ + public static URL getImageURL(String key) + { + if (getImage(key) != null) + { + if (urlMap().containsKey(key)) + { + return urlMap().getOrDefault(key, null); + } + System.err.println( + "Do not use getImageURL(key) before using getImage(key...)"); + } + return null; + } + + /* + * Get a List of Icon images of different sizes. + */ + public static ArrayList getIconList() + { + return iconList; + } +} diff --git a/getdown/src/getdown/core/src/main/java/jalview/util/StringUtils.java b/getdown/src/getdown/core/src/main/java/jalview/util/StringUtils.java new file mode 100644 index 0000000..d758395 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/jalview/util/StringUtils.java @@ -0,0 +1,585 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class StringUtils +{ + private static final Pattern DELIMITERS_PATTERN = Pattern + .compile(".*='[^']*(?!')"); + + private static final char PERCENT = '%'; + + private static final boolean DEBUG = false; + + /* + * URL encoded characters, indexed by char value + * e.g. urlEncodings['='] = urlEncodings[61] = "%3D" + */ + private static String[] urlEncodings = new String[255]; + + /** + * Returns a new character array, after inserting characters into the given + * character array. + * + * @param in + * the character array to insert into + * @param position + * the 0-based position for insertion + * @param count + * the number of characters to insert + * @param ch + * the character to insert + */ + public static final char[] insertCharAt(char[] in, int position, + int count, char ch) + { + char[] tmp = new char[in.length + count]; + + if (position >= in.length) + { + System.arraycopy(in, 0, tmp, 0, in.length); + position = in.length; + } + else + { + System.arraycopy(in, 0, tmp, 0, position); + } + + int index = position; + while (count > 0) + { + tmp[index++] = ch; + count--; + } + + if (position < in.length) + { + System.arraycopy(in, position, tmp, index, in.length - position); + } + + return tmp; + } + + /** + * Delete + * + * @param in + * @param from + * @param to + * @return + */ + public static final char[] deleteChars(char[] in, int from, int to) + { + if (from >= in.length || from < 0) + { + return in; + } + + char[] tmp; + + if (to >= in.length) + { + tmp = new char[from]; + System.arraycopy(in, 0, tmp, 0, from); + to = in.length; + } + else + { + tmp = new char[in.length - to + from]; + System.arraycopy(in, 0, tmp, 0, from); + System.arraycopy(in, to, tmp, from, in.length - to); + } + return tmp; + } + + /** + * Returns the last part of 'input' after the last occurrence of 'token'. For + * example to extract only the filename from a full path or URL. + * + * @param input + * @param token + * a delimiter which must be in regular expression format + * @return + */ + public static String getLastToken(String input, String token) + { + if (input == null) + { + return null; + } + if (token == null) + { + return input; + } + String[] st = input.split(token); + return st[st.length - 1]; + } + + /** + * Parses the input string into components separated by the delimiter. Unlike + * String.split(), this method will ignore occurrences of the delimiter which + * are nested within single quotes in name-value pair values, e.g. a='b,c'. + * + * @param input + * @param delimiter + * @return elements separated by separator + */ + public static String[] separatorListToArray(String input, + String delimiter) + { + int seplen = delimiter.length(); + if (input == null || input.equals("") || input.equals(delimiter)) + { + return null; + } + List jv = new ArrayList<>(); + int cp = 0, pos, escape; + boolean wasescaped = false, wasquoted = false; + String lstitem = null; + while ((pos = input.indexOf(delimiter, cp)) >= cp) + { + escape = (pos > 0 && input.charAt(pos - 1) == '\\') ? -1 : 0; + if (wasescaped || wasquoted) + { + // append to previous pos + jv.set(jv.size() - 1, lstitem = lstitem + delimiter + + input.substring(cp, pos + escape)); + } + else + { + jv.add(lstitem = input.substring(cp, pos + escape)); + } + cp = pos + seplen; + wasescaped = escape == -1; + // last separator may be in an unmatched quote + wasquoted = DELIMITERS_PATTERN.matcher(lstitem).matches(); + } + if (cp < input.length()) + { + String c = input.substring(cp); + if (wasescaped || wasquoted) + { + // append final separator + jv.set(jv.size() - 1, lstitem + delimiter + c); + } + else + { + if (!c.equals(delimiter)) + { + jv.add(c); + } + } + } + if (jv.size() > 0) + { + String[] v = jv.toArray(new String[jv.size()]); + jv.clear(); + if (DEBUG) + { + System.err.println("Array from '" + delimiter + + "' separated List:\n" + v.length); + for (int i = 0; i < v.length; i++) + { + System.err.println("item " + i + " '" + v[i] + "'"); + } + } + return v; + } + if (DEBUG) + { + System.err.println( + "Empty Array from '" + delimiter + "' separated List"); + } + return null; + } + + /** + * Returns a string which contains the list elements delimited by the + * separator. Null items are ignored. If the input is null or has length zero, + * a single delimiter is returned. + * + * @param list + * @param separator + * @return concatenated string + */ + public static String arrayToSeparatorList(String[] list, String separator) + { + StringBuffer v = new StringBuffer(); + if (list != null && list.length > 0) + { + for (int i = 0, iSize = list.length; i < iSize; i++) + { + if (list[i] != null) + { + if (v.length() > 0) + { + v.append(separator); + } + // TODO - escape any separator values in list[i] + v.append(list[i]); + } + } + if (DEBUG) + { + System.err + .println("Returning '" + separator + "' separated List:\n"); + System.err.println(v); + } + return v.toString(); + } + if (DEBUG) + { + System.err.println( + "Returning empty '" + separator + "' separated List\n"); + } + return "" + separator; + } + + /** + * Converts a list to a string with a delimiter before each term except the + * first. Returns an empty string given a null or zero-length argument. This + * can be replaced with StringJoiner in Java 8. + * + * @param terms + * @param delim + * @return + */ + public static String listToDelimitedString(List terms, + String delim) + { + StringBuilder sb = new StringBuilder(32); + if (terms != null && !terms.isEmpty()) + { + boolean appended = false; + for (String term : terms) + { + if (appended) + { + sb.append(delim); + } + appended = true; + sb.append(term); + } + } + return sb.toString(); + } + + /** + * Convenience method to parse a string to an integer, returning 0 if the + * input is null or not a valid integer + * + * @param s + * @return + */ + public static int parseInt(String s) + { + int result = 0; + if (s != null && s.length() > 0) + { + try + { + result = Integer.parseInt(s); + } catch (NumberFormatException ex) + { + } + } + return result; + } + + /** + * Compares two versions formatted as e.g. "3.4.5" and returns -1, 0 or 1 as + * the first version precedes, is equal to, or follows the second + * + * @param v1 + * @param v2 + * @return + */ + public static int compareVersions(String v1, String v2) + { + return compareVersions(v1, v2, null); + } + + /** + * Compares two versions formatted as e.g. "3.4.5b1" and returns -1, 0 or 1 as + * the first version precedes, is equal to, or follows the second + * + * @param v1 + * @param v2 + * @param pointSeparator + * a string used to delimit point increments in sub-tokens of the + * version + * @return + */ + public static int compareVersions(String v1, String v2, + String pointSeparator) + { + if (v1 == null || v2 == null) + { + return 0; + } + String[] toks1 = v1.split("\\."); + String[] toks2 = v2.split("\\."); + int i = 0; + for (; i < toks1.length; i++) + { + if (i >= toks2.length) + { + /* + * extra tokens in v1 + */ + return 1; + } + String tok1 = toks1[i]; + String tok2 = toks2[i]; + if (pointSeparator != null) + { + /* + * convert e.g. 5b2 into decimal 5.2 for comparison purposes + */ + tok1 = tok1.replace(pointSeparator, "."); + tok2 = tok2.replace(pointSeparator, "."); + } + try + { + float f1 = Float.valueOf(tok1); + float f2 = Float.valueOf(tok2); + int comp = Float.compare(f1, f2); + if (comp != 0) + { + return comp; + } + } catch (NumberFormatException e) + { + System.err + .println("Invalid version format found: " + e.getMessage()); + return 0; + } + } + + if (i < toks2.length) + { + /* + * extra tokens in v2 + */ + return -1; + } + + /* + * same length, all tokens match + */ + return 0; + } + + /** + * Converts the string to all lower-case except the first character which is + * upper-cased + * + * @param s + * @return + */ + public static String toSentenceCase(String s) + { + if (s == null) + { + return s; + } + if (s.length() <= 1) + { + return s.toUpperCase(); + } + return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + } + + /** + * A helper method that strips off any leading or trailing html and body tags. + * If no html tag is found, then also html-encodes angle bracket characters. + * + * @param text + * @return + */ + public static String stripHtmlTags(String text) + { + if (text == null) + { + return null; + } + String tmp2up = text.toUpperCase(); + int startTag = tmp2up.indexOf(""); + if (startTag > -1) + { + text = text.substring(startTag + 6); + tmp2up = tmp2up.substring(startTag + 6); + } + // is omission of "" intentional here?? + int endTag = tmp2up.indexOf(""); + if (endTag > -1) + { + text = text.substring(0, endTag); + tmp2up = tmp2up.substring(0, endTag); + } + endTag = tmp2up.indexOf(""); + if (endTag > -1) + { + text = text.substring(0, endTag); + } + + if (startTag == -1 && (text.contains("<") || text.contains(">"))) + { + text = text.replaceAll("<", "<"); + text = text.replaceAll(">", ">"); + } + return text; + } + + /** + * Answers the input string with any occurrences of the 'encodeable' + * characters replaced by their URL encoding + * + * @param s + * @param encodable + * @return + */ + public static String urlEncode(String s, String encodable) + { + if (s == null || s.isEmpty()) + { + return s; + } + + /* + * do % encoding first, as otherwise it may double-encode! + */ + if (encodable.indexOf(PERCENT) != -1) + { + s = urlEncode(s, PERCENT); + } + + for (char c : encodable.toCharArray()) + { + if (c != PERCENT) + { + s = urlEncode(s, c); + } + } + return s; + } + + /** + * Answers the input string with any occurrences of {@code c} replaced with + * their url encoding. Answers the input string if it is unchanged. + * + * @param s + * @param c + * @return + */ + static String urlEncode(String s, char c) + { + String decoded = String.valueOf(c); + if (s.indexOf(decoded) != -1) + { + String encoded = getUrlEncoding(c); + if (!encoded.equals(decoded)) + { + s = s.replace(decoded, encoded); + } + } + return s; + } + + /** + * Answers the input string with any occurrences of the specified (unencoded) + * characters replaced by their URL decoding. + *

+ * Example: {@code urlDecode("a%3Db%3Bc", "-;=,")} should answer + * {@code "a=b;c"}. + * + * @param s + * @param encodable + * @return + */ + public static String urlDecode(String s, String encodable) + { + if (s == null || s.isEmpty()) + { + return s; + } + + for (char c : encodable.toCharArray()) + { + String encoded = getUrlEncoding(c); + if (s.indexOf(encoded) != -1) + { + String decoded = String.valueOf(c); + s = s.replace(encoded, decoded); + } + } + return s; + } + + /** + * Does a lazy lookup of the url encoding of the given character, saving the + * value for repeat lookups + * + * @param c + * @return + */ + private static String getUrlEncoding(char c) + { + if (c < 0 || c >= urlEncodings.length) + { + return String.valueOf(c); + } + + String enc = urlEncodings[c]; + if (enc == null) + { + try + { + enc = urlEncodings[c] = URLEncoder.encode(String.valueOf(c), + "UTF-8"); + } catch (UnsupportedEncodingException e) + { + enc = urlEncodings[c] = String.valueOf(c); + } + } + return enc; + } + + public static int firstCharPosIgnoreCase(String text, String chars) + { + int min = text.length() + 1; + for (char c : chars.toLowerCase().toCharArray()) + { + int i = text.toLowerCase().indexOf(c); + if (0 <= i && i < min) + { + min = i; + } + } + return min < text.length() + 1 ? min : -1; + } +} diff --git a/getdown/src/getdown/launcher/pom.xml b/getdown/src/getdown/launcher/pom.xml index b5e68f2..5284412 100644 --- a/getdown/src/getdown/launcher/pom.xml +++ b/getdown/src/getdown/launcher/pom.xml @@ -4,7 +4,7 @@ com.threerings.getdown getdown - 1.8.3-1.2.10_FJVL + 1.8.3-1.2.11_FJVL getdown-launcher diff --git a/getdown/src/getdown/mvn_cmd b/getdown/src/getdown/mvn_cmd index 65e5fb9..4ffb086 100755 --- a/getdown/src/getdown/mvn_cmd +++ b/getdown/src/getdown/mvn_cmd @@ -3,7 +3,7 @@ if [ x$JVLVERSION != x ]; then export VERSION=$JVLVERSION else - export VERSION=1.8.3-1.2.10_JVL + export VERSION=1.8.3-1.2.11_JVL fi if [ x${VERSION%_JVL} = x$VERSION ]; then diff --git a/getdown/src/getdown/pom.xml b/getdown/src/getdown/pom.xml index 7a0fd27..61b1440 100644 --- a/getdown/src/getdown/pom.xml +++ b/getdown/src/getdown/pom.xml @@ -10,7 +10,7 @@ com.threerings.getdown getdown pom - 1.8.3-1.2.10_FJVL + 1.8.3-1.2.11_FJVL getdown An application installer and updater. diff --git a/gradle.properties b/gradle.properties index 3088b48..50eaa5d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -112,6 +112,7 @@ j11modules = com.sun.istack.runtime,com.sun.xml.bind,com.sun.xml.fastinfoset,com flexmark_css = utils/doc/github.css channel_properties_dir = utils/channels +channel_props = channel.props install4j_home_dir = ~/buildtools/install4j8 install4j_copyright_message = ... diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index d8217d2..13fdc93 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -1390,3 +1390,14 @@ label.log_level = Log level label.log_level_tooltip = Temporarily set the log level for this console. The log level will revert to {0} when this Java console is closed. label.copy_to_clipboard = Copy to clipboard label.copy_to_clipboard_tooltip = Copy all of the log text in this console to the system clipboard +label.startup = Startup +label.memory = Memory +label.customise_memory_settings = Customise maximum memory settings +label.memory_setting_text = New memory settings will only come into effect the next time you start Jalview +label.maximum_memory_used = Maximum memory limited to both +label.percent_of_physical_memory = Maximum percent of physical memory +label.maximum_memory = Maximum absolute memory +label.maximum_memory_tooltip = Enter memory as an integer number optionally followed by 'b', 'k', 'm', 'g' or 't' +label.adjustments_for_this_computer = Adjustments for this computer +label.memory_example_text = Maximum memory that would be used with these settings on this computer +label.memory_example_tooltip = The memory allocated to Jalview is the smaller of the percentage of physical memory (default 90%) and the maximum absolute memory (default 32GB). If your computer's memory cannot be ascertained then the maximum absolute memory defaults to 8GB (if not customised).
Jalview will always try and reserve 512MB for the OS and at least 512MB for itself. diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index fdf4201..c99626b 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -1389,3 +1389,14 @@ label.log_level = Nivel del registro label.log_level_tooltip = Establezca temporalmente el nivel de registro para esta consola. El nivel de registro volverá a {0} cuando se cierre esta consola de Java. label.copy_to_clipboard = Copiar en el portapapeles label.copy_to_clipboard_tooltip = Copie todo el texto de registro en esta consola al portapapeles del sistema +label.startup = Inicio +label.memory = Memoria +label.customise_memory_settings = Personalizar la configuración de memoria máxima +label.memory_setting_text = La nueva configuración de memoria solo entrará en vigor la próxima vez que inicie Jalview +label.maximum_memory_used = Memoria máxima limitada a ambos +label.percent_of_physical_memory = Porcentaje máximo de memoria física +label.maximum_memory = Memoria absoluta máxima +label.maximum_memory_tooltip = Ingrese la memoria como un número entero seguido opcionalmente por 'b', 'k', 'm', 'g' o 't' +label.adjustments_for_this_computer = Ajustes para esta computadora +label.memory_example_text = Memoria máxima que se usaría con esta configuración en esta computadora +label.memory_example_tooltip = La memoria asignada a Jalview es el menor entre el porcentaje de memoria física (predeterminado 90%) y la memoria absoluta máxima (predeterminado 32 GB). Si no se puede determinar la memoria de su computadora, la memoria absoluta máxima predeterminada es de 8 GB (si no está personalizada).
Jalview siempre intentará reservar 512 MB para el sistema operativo y al menos 512 MB para sí mismo. diff --git a/src/jalview/bin/LaunchUtils.java b/src/jalview/bin/LaunchUtils.java new file mode 100644 index 0000000..89d6069 --- /dev/null +++ b/src/jalview/bin/LaunchUtils.java @@ -0,0 +1,41 @@ +package jalview.bin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; + +import jalview.util.ChannelProperties; + +public class LaunchUtils +{ + + private static Properties userPreferences = null; + + public static String getUserPreference(String key) + { + if (userPreferences == null) + { + String channelPrefsFilename = ChannelProperties + .getProperty("preferences.filename"); + String propertiesFile = System.getProperty("user.home") + + File.separatorChar + channelPrefsFilename; + try + { + userPreferences = new Properties(); + userPreferences.load(new FileInputStream(propertiesFile)); + } catch (FileNotFoundException e) + { + // didn't find user preferences file + return null; + } catch (IOException e) + { + System.err.println(e.getMessage()); + return null; + } + } + return userPreferences.getProperty(key); + } + +} diff --git a/src/jalview/bin/Launcher.java b/src/jalview/bin/Launcher.java index b8d31c2..fe4b7ef 100644 --- a/src/jalview/bin/Launcher.java +++ b/src/jalview/bin/Launcher.java @@ -104,6 +104,23 @@ public class Launcher } } + // use saved preferences if no cmdline args + boolean useCustomisedSettings = Boolean.parseBoolean(LaunchUtils + .getUserPreference(MemorySetting.CUSTOMISED_SETTINGS)); + if (useCustomisedSettings) + { + if (jvmmempc == null) + { + jvmmempc = LaunchUtils + .getUserPreference(MemorySetting.MEMORY_JVMMEMPC); + } + if (jvmmemmax == null) + { + jvmmemmax = LaunchUtils + .getUserPreference(MemorySetting.MEMORY_JVMMEMMAX); + } + } + // add memory setting if not specified boolean memSet = false; boolean dockIcon = false; @@ -226,7 +243,6 @@ public class Launcher e.printStackTrace(); } // System.exit(0); - } } diff --git a/src/jalview/bin/MemorySetting.java b/src/jalview/bin/MemorySetting.java index 5d7f14c..55e304d 100644 --- a/src/jalview/bin/MemorySetting.java +++ b/src/jalview/bin/MemorySetting.java @@ -1,4 +1,6 @@ /* + + private static String ADJUSTMENT_MESSAGE = null; * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * @@ -31,6 +33,7 @@ package jalview.bin; * @author bsoares * */ + public class MemorySetting { public static final String MAX_HEAPSIZE_PERCENT_PROPERTY_NAME = "jvmmempc"; @@ -49,13 +52,33 @@ public class MemorySetting private static final long NOMEM_MAX_HEAPSIZE_GB_DEFAULT = 8; + public static final String NS = "MEMORY"; + + public static final String CUSTOMISED_SETTINGS = NS + + "_CUSTOMISED_SETTINGS"; + + public static final String MEMORY_JVMMEMPC = NS + "_" + + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.toUpperCase(); + + public static final String MEMORY_JVMMEMMAX = NS + "_" + + MAX_HEAPSIZE_PROPERTY_NAME.toUpperCase(); + protected static boolean logToClassChecked = false; + public static String memorySuffixes = "bkmgt"; // order of the suffixes is + // important! + public static long getMemorySetting() { return getMemorySetting(null, null); } + public static long getMemorySetting(String jvmmemmaxarg, + String jvmmempcarg) + { + return getMemorySetting(jvmmemmaxarg, jvmmempcarg, true, false); + } + /** * Decide on appropriate memory setting for Jalview based on the two arguments * values: jvmmempc - the maximum percentage of total physical memory to @@ -83,99 +106,66 @@ public class MemorySetting * @param jvmmempcarg * Max percentage of physical memory to use. Defaults to "90". * + * @param useProps + * boolean to decide whether to look at System properties. + * * @return The amount of memory (in bytes) to allocate to Jalview */ public static long getMemorySetting(String jvmmemmaxarg, - String jvmmempcarg) + String jvmmempcarg, boolean useProps, boolean quiet) { // actual Xmx value-to-be long maxMemLong = -1; + clearAdjustmentMessage(); // (absolute) jvmmaxmem setting, start with default long memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; - if (jvmmemmaxarg == null) + if (jvmmemmaxarg == null && useProps) { jvmmemmaxarg = System.getProperty(MAX_HEAPSIZE_PROPERTY_NAME); } String jvmmemmax = jvmmemmaxarg; if (jvmmemmax != null && jvmmemmax.length() > 0) { - long multiplier = 1; - switch (jvmmemmax.toLowerCase().substring(jvmmemmax.length() - 1)) - { - case "t": - multiplier = 1099511627776L; // 2^40 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "g": - multiplier = 1073741824; // 2^30 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "m": - multiplier = 1048576; // 2^20 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "k": - multiplier = 1024; // 2^10 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - case "b": - multiplier = 1; // 2^0 - jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1); - break; - default: - break; - } - // parse the arg try { - memmax = Long.parseLong(jvmmemmax); + memmax = memoryStringToLong(jvmmemmax); + if (memmax == 0) + { + throw (new NumberFormatException("Not allowing 0")); + } } catch (NumberFormatException e) { memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg - + "') badly formatted, using default (" - + MAX_HEAPSIZE_GB_DEFAULT + "g)."); - } - - // apply multiplier if not too big (i.e. bigger than a long) - if (Long.MAX_VALUE / memmax < multiplier) - { - memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; - System.out.println("MemorySetting Property '" - + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg - + ") too big, using default (" + MAX_HEAPSIZE_GB_DEFAULT - + "g)."); - } - else - { - memmax = multiplier * memmax; + + "') badly formatted or 0, using default (" + + MAX_HEAPSIZE_GB_DEFAULT + "g).", quiet); } // check at least minimum value (this accounts for negatives too) if (memmax < APPLICATION_MIN_MEMORY) { memmax = APPLICATION_MIN_MEMORY; - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg + ") too small, using minimum (" + APPLICATION_MIN_MEMORY - + ")."); + + ").", quiet); } } else { // no need to warn if no setting - // System.out.println("MemorySetting Property '" + maxHeapSizeProperty + // adjustmentMessage("MemorySetting Property '" + maxHeapSizeProperty // + "' not // set."); } // get max percent of physical memory, starting with default float percent = MAX_HEAPSIZE_PERCENT_DEFAULT; - if (jvmmempcarg == null) + if (jvmmempcarg == null && useProps) { jvmmempcarg = System.getProperty(MAX_HEAPSIZE_PERCENT_PROPERTY_NAME); } @@ -185,24 +175,24 @@ public class MemorySetting { if (jvmmempc != null) { - float trypercent = Float.parseFloat(jvmmempc); - if (0 < trypercent && trypercent <= 100f) + int trypercent = Integer.parseInt(jvmmempc); + if (0 <= trypercent && trypercent <= 100) { percent = trypercent; } else { - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME - + "' should be in range 1..100. Using default " + percent - + "%"); + + "' should be in range 0..100. Using default " + percent + + "%", quiet); } } } catch (NumberFormatException e) { - System.out.println("MemorySetting property '" + setAdjustmentMessage("MemorySetting property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg - + ") badly formatted"); + + ") badly formatted", quiet); } // catch everything in case of no com.sun.management.OperatingSystemMXBean @@ -223,10 +213,10 @@ public class MemorySetting { mempc = physicalMem - LEAVE_FREE_MIN_MEMORY; reducedmempc = true; - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg + ") too large. Leaving free space for OS and reducing to (" - + mempc + ")."); + + mempc + ").", quiet); } // check for minimum application memsize @@ -234,16 +224,16 @@ public class MemorySetting { if (reducedmempc) { - System.out.println("Reduced MemorySetting (" + mempc + setAdjustmentMessage("Reduced MemorySetting (" + mempc + ") too small. Increasing to application minimum (" - + APPLICATION_MIN_MEMORY + ")."); + + APPLICATION_MIN_MEMORY + ").", quiet); } else { - System.out.println("MemorySetting Property '" + setAdjustmentMessage("MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg + ") too small. Using minimum (" - + APPLICATION_MIN_MEMORY + ")."); + + APPLICATION_MIN_MEMORY + ").", quiet); } mempc = APPLICATION_MIN_MEMORY; } @@ -252,19 +242,21 @@ public class MemorySetting { // not enough memory for application, just try and grab what we can! mempc = physicalMem; - System.out.println( + setAdjustmentMessage( "Not enough physical memory for application. Ignoring MemorySetting Property '" + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg + "). Using maximum memory available (" - + physicalMem + ")."); + + physicalMem + ").", + quiet); } } catch (Throwable t) { memoryPercentError = true; - System.out.println( - "Problem calling GetMemory.getPhysicalMemory(). Likely to be problem with com.sun.management.OperatingSystemMXBean"); + setAdjustmentMessage( + "Problem calling GetMemory.getPhysicalMemory(). Likely to be problem with com.sun.management.OperatingSystemMXBean", + quiet); t.printStackTrace(); } @@ -281,9 +273,10 @@ public class MemorySetting // == null)) && memmax > NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE) { - System.out.println( + setAdjustmentMessage( "Capping maximum memory to " + NOMEM_MAX_HEAPSIZE_GB_DEFAULT - + "g due to failure to read physical memory size."); + + "g due to failure to read physical memory size.", + quiet); memmax = NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE; } @@ -299,4 +292,120 @@ public class MemorySetting return maxMemLong; } + public static boolean isValidMemoryString(String text) + { + if (text.length() > 0) + { + char lastChar = text.charAt(text.length() - 1); + char[] otherChars = text.substring(0, text.length() - 1) + .toCharArray(); + for (char c : otherChars) + { + if (c < '0' || c > '9') + { + return false; + } + } + if ((lastChar < '0' || lastChar > '9') && memorySuffixes + .indexOf(Character.toLowerCase(lastChar)) == -1) + { + return false; + } + } + return true; + } + + public static long memoryStringToLong(String memString) + throws NumberFormatException + { + if (!isValidMemoryString(memString)) // not valid + { + throw (new NumberFormatException("Not a valid memory string")); + } + char suffix = Character + .toLowerCase(memString.charAt(memString.length() - 1)); + if ('0' <= suffix && suffix <= '9') // no suffix + { + return Long.valueOf(memString); + } + if (memorySuffixes.indexOf(suffix) == -1) // suffix is unknown + { + return -1; + } + + long multiplier = (long) Math.pow(2, + memorySuffixes.indexOf(suffix) * 10); // note order of suffixes in + // memorySuffixes important + // here! + // parse the arg. NumberFormatExceptions passed on to calling method + long mem = Long + .parseLong(memString.substring(0, memString.length() - 1)); + if (mem == 0) + { + return 0; + } + + // apply multiplier only if result is not too big (i.e. bigger than a long) + if (Long.MAX_VALUE / mem > multiplier) + { + return multiplier * mem; + } + else + { + // number too big for a Long. Limit to Long.MAX_VALUE + System.out.println("Memory parsing of '" + memString + + "' produces number too big. Limiting to Long.MAX_VALUE=" + + Long.MAX_VALUE); + return Long.MAX_VALUE; + } + } + + public static String memoryLongToString(long mem) + { + return memoryLongToString(mem, "%.1f"); + } + + public static String memoryLongToString(long mem, String format) + { + int exponent = 0; + float num = mem; + char suffix = 'b'; + + for (int i = 0; i < memorySuffixes.length(); i++) + { + char s = Character.toUpperCase(memorySuffixes.charAt(i)); + if (mem < (long) Math.pow(2, exponent + 10) + || i == memorySuffixes.length() - 1) // last suffix + { + suffix = s; + num = (float) (mem / Math.pow(2, exponent)); + break; + } + exponent += 10; + } + + return String.format(format, num) + suffix; + } + + private static String ADJUSTMENT_MESSAGE = null; + + private static void setAdjustmentMessage(String reason, boolean quiet) + { + ADJUSTMENT_MESSAGE = reason; + if (!quiet) + { + System.out.println(reason); + } + } + + public static void clearAdjustmentMessage() + { + ADJUSTMENT_MESSAGE = null; + } + + public static String getAdjustmentMessage() + { + return ADJUSTMENT_MESSAGE; + } + } \ No newline at end of file diff --git a/src/jalview/gui/Preferences.java b/src/jalview/gui/Preferences.java index 6972657..8eb0664 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -53,6 +53,7 @@ import javax.swing.table.TableRowSorter; import ext.edu.ucsf.rbvi.strucviz2.StructureManager; import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; import jalview.bin.Cache; +import jalview.bin.MemorySetting; import jalview.ext.pymol.PymolManager; import jalview.gui.Help.HelpId; import jalview.gui.StructureViewer.ViewerType; @@ -660,6 +661,11 @@ public class Preferences extends GPreferences * Set Backups tab defaults */ loadLastSavedBackupsOptions(); + + /* + * Set Startup tab defaults + */ + } /** @@ -974,6 +980,21 @@ public class Preferences extends GPreferences Cache.applicationProperties.setProperty( BackupFilesPresetEntry.SAVEDCONFIG, savedBFPE.toString()); + /* + * Save Memory Settings + */ + Cache.applicationProperties.setProperty( + MemorySetting.CUSTOMISED_SETTINGS, + Boolean.toString(customiseMemorySetting.isSelected())); + Cache.applicationProperties.setProperty(MemorySetting.MEMORY_JVMMEMPC, + Integer.toString(jvmMemoryPercentSlider.getValue())); + Cache.applicationProperties.setProperty(MemorySetting.MEMORY_JVMMEMMAX, + jvmMemoryMaxTextField.getText()); + + /* + * save and close Preferences + */ + Cache.saveProperties(); Desktop.instance.doConfigureStructurePrefs(); try diff --git a/src/jalview/jbgui/GPreferences.java b/src/jalview/jbgui/GPreferences.java index 1a4a44b..dce8c5e 100755 --- a/src/jalview/jbgui/GPreferences.java +++ b/src/jalview/jbgui/GPreferences.java @@ -43,6 +43,7 @@ import java.util.List; import javax.swing.AbstractCellEditor; import javax.swing.BorderFactory; +import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; @@ -54,6 +55,7 @@ import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JRadioButton; import javax.swing.JScrollPane; +import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.JTable; @@ -75,6 +77,7 @@ import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import jalview.bin.Cache; +import jalview.bin.MemorySetting; import jalview.fts.core.FTSDataColumnPreferences; import jalview.fts.core.FTSDataColumnPreferences.PreferenceSource; import jalview.fts.service.pdb.PDBFTSRestClient; @@ -89,6 +92,7 @@ import jalview.io.BackupFilesPresetEntry; import jalview.io.IntKeyStringValueEntry; import jalview.util.MessageManager; import jalview.util.Platform; +import jalview.util.StringUtils; /** * Base class for the Preferences panel. @@ -381,6 +385,30 @@ public class GPreferences extends JPanel private JLabel messageLabel = new JLabel("", JLabel.CENTER); + /* + * Startup tab components + */ + + protected JCheckBox customiseMemorySetting = new JCheckBox(); + + protected JLabel exampleMemoryLabel = new JLabel(); + + protected JTextArea exampleMemoryMessageTextArea = new JTextArea(); + + protected JLabel maxMemoryLabel = new JLabel(); + + protected JLabel jvmMemoryPercentLabel = new JLabel(); + + protected JSlider jvmMemoryPercentSlider = new JSlider(); + + protected JLabel jvmMemoryPercentDisplay = new JLabel(); + + protected JLabel jvmMemoryMaxLabel = new JLabel(); + + protected JTextField jvmMemoryMaxTextField = new JTextField(null, 8); + + protected JComboBox lafCombo = new JComboBox<>(); + /** * Creates a new GPreferences object. */ @@ -442,6 +470,9 @@ public class GPreferences extends JPanel tabbedPane.add(initEditingTab(), MessageManager.getString("label.editing")); + tabbedPane.add(initStartupTab(), + MessageManager.getString("label.startup")); + /* * See WsPreferences for the real work of configuring this tab. */ @@ -2203,6 +2234,19 @@ public class GPreferences extends JPanel updateBackupFilesExampleLabel(); } + /* + * Load the saved Memory settings + */ + protected void loadLastSavedMemorySettings() + { + customiseMemorySetting.setSelected( + Cache.getDefault(MemorySetting.CUSTOMISED_SETTINGS, false)); + jvmMemoryPercentSlider + .setValue(Cache.getDefault(MemorySetting.MEMORY_JVMMEMPC, 90)); + jvmMemoryMaxTextField.setText( + Cache.getDefault(MemorySetting.MEMORY_JVMMEMMAX, "32g")); + } + private boolean warnAboutSuffixReverseChange() { BackupFilesPresetEntry bfpe = BackupFilesPresetEntry @@ -2220,6 +2264,284 @@ public class GPreferences extends JPanel && nowSuffixTemplate.equals(savedSuffixTemplate); } + /* Initialises the Startup tabbed panel. + * + * @return + * */ + + private JPanel initStartupTab() + { + JPanel startupTab = new JPanel(); + startupTab.setBorder( + new TitledBorder(MessageManager.getString("label.memory"))); + startupTab.setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.anchor = GridBagConstraints.FIRST_LINE_START; + gbc.fill = GridBagConstraints.NONE; + + initMemoryPanel(); + + gbc.gridheight = 1; + gbc.gridwidth = 3; + + gbc.gridy = 0; // row 1 + gbc.gridx = 0; + JLabel memoryText = new JLabel(); + memoryText.setFont(LABEL_FONT_ITALIC); + memoryText + .setText(MessageManager.getString("label.memory_setting_text")); + startupTab.add(memoryText, gbc); + + gbc.gridy++; // row 2 + gbc.gridx = 0; + JPanel exampleMemoryPanel = new JPanel(); + exampleMemoryPanel + .setLayout(new BoxLayout(exampleMemoryPanel, BoxLayout.Y_AXIS)); + exampleMemoryPanel.setToolTipText(JvSwingUtils.wrapTooltip(true, + MessageManager.getString("label.memory_example_tooltip"))); + JLabel exampleTextLabel = new JLabel(); + exampleTextLabel + .setText(MessageManager.getString("label.memory_example_text")); + exampleTextLabel.setForeground(Color.GRAY); + exampleTextLabel.setFont(LABEL_FONT); + exampleMemoryPanel.add(exampleTextLabel); + exampleMemoryPanel.add(exampleMemoryLabel); + exampleMemoryPanel.setBackground(Color.WHITE); + exampleMemoryPanel.setBorder(BorderFactory.createEtchedBorder()); + startupTab.add(exampleMemoryPanel, gbc); + + gbc.gridy++; // row 3 + gbc.gridx = 0; + startupTab.add(customiseMemorySetting, gbc); + + gbc.gridy += 2; // row 4 with a gap + gbc.gridx = 0; + startupTab.add(maxMemoryLabel, gbc); + + gbc.gridy += 2; // row 5 + gbc.gridx = 0; + gbc.gridwidth = 1; + startupTab.add(jvmMemoryPercentLabel, gbc); + gbc.gridx++; + startupTab.add(jvmMemoryPercentSlider, gbc); + gbc.gridx++; + // gbc.weightx = 0.1; + startupTab.add(jvmMemoryPercentDisplay, gbc); + // gbc.weightx = 1.0; + gbc.gridwidth = 3; + + gbc.gridy++; // row 6 + gbc.gridx = 0; + startupTab.add(jvmMemoryMaxLabel, gbc); + gbc.gridx++; + startupTab.add(jvmMemoryMaxTextField, gbc); + + gbc.gridy++; // row 7 + gbc.gridx = 0; + gbc.gridwidth = 4; + exampleMemoryMessageTextArea.setBackground(startupTab.getBackground()); + JScrollPane sp = new JScrollPane(exampleMemoryMessageTextArea); + sp.setBorder(BorderFactory.createEmptyBorder()); + sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); + startupTab.add(sp, gbc); + + // fill empty space to push widget to top left + gbc.gridy++; + gbc.weighty = 1.0; + gbc.gridx = 100; + gbc.gridwidth = 1; + gbc.weightx = 1.0; + startupTab.add(new JPanel(), gbc); + + setMemoryPercentDisplay(); + memoryOptionsSetEnabled(); + return startupTab; + } + + private void initMemoryPanel() + { + // Enable memory settings checkbox + customiseMemorySetting.setFont(LABEL_FONT_BOLD); + customiseMemorySetting.setText( + MessageManager.getString("label.customise_memory_settings")); + customiseMemorySetting.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + memoryOptionsSetEnabled(); + } + }); + + loadLastSavedMemorySettings(); + + exampleMemoryLabel.setFont(LABEL_FONT_BOLD); + exampleMemoryLabel.setBackground(Color.WHITE); + + maxMemoryLabel = new JLabel( + MessageManager.getString("label.maximum_memory_used")); + maxMemoryLabel.setFont(LABEL_FONT_BOLD); + + // Maximum memory percentage slider + jvmMemoryPercentLabel.setFont(LABEL_FONT); + jvmMemoryPercentLabel.setText( + MessageManager.getString("label.percent_of_physical_memory")); + jvmMemoryPercentSlider.setPaintLabels(true); + jvmMemoryPercentSlider.setPaintTicks(true); + jvmMemoryPercentSlider.setPaintTrack(true); + jvmMemoryPercentSlider.setMajorTickSpacing(50); + jvmMemoryPercentSlider.setMinorTickSpacing(10); + jvmMemoryPercentSlider.addChangeListener(new ChangeListener() + { + @Override + public void stateChanged(ChangeEvent e) + { + setMemoryPercentDisplay(); + } + }); + jvmMemoryPercentDisplay.setFont(LABEL_FONT); + setMemoryPercentDisplay(); + + // Maximum memory cap textbox + jvmMemoryMaxLabel.setFont(LABEL_FONT); + jvmMemoryMaxLabel + .setText(MessageManager.getString("label.maximum_memory")); + initMemoryMaxTextField(); + + exampleMemoryMessageTextArea.setFont(LABEL_FONT_ITALIC); + exampleMemoryMessageTextArea.setForeground(Color.GRAY); + exampleMemoryMessageTextArea.setEditable(false); + exampleMemoryMessageTextArea.setLineWrap(true); + exampleMemoryMessageTextArea.setWrapStyleWord(true); + exampleMemoryMessageTextArea.setText(" "); + exampleMemoryMessageTextArea.setRows(2); + exampleMemoryMessageTextArea.setColumns(40); + + setExampleMemoryLabel(); + } + + private void initMemoryMaxTextField() + { + jvmMemoryMaxTextField.setToolTipText( + MessageManager.getString("label.maximum_memory_tooltip")); + jvmMemoryMaxTextField.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent arg0) + { + validateMemoryMaxTextField(); + setExampleMemoryLabel(); + } + }); + + jvmMemoryMaxTextField.addKeyListener(new KeyListener() + { + @Override + public void keyReleased(KeyEvent e) + { + validateMemoryMaxTextField(); + setExampleMemoryLabel(); + } + + @Override + public void keyPressed(KeyEvent e) + { + } + + // try and stop invalid typing + @Override + public void keyTyped(KeyEvent e) + { + char c = Character.toLowerCase(e.getKeyChar()); + String text = jvmMemoryMaxTextField.getText(); + String suffixes = "tgmkb"; + int pos = jvmMemoryMaxTextField.getCaretPosition(); + int suffixPos = StringUtils.firstCharPosIgnoreCase(text, suffixes); + if (!((('0' <= c && c <= '9') + && (suffixPos == -1 || pos <= suffixPos)) // digits only allowed + // before suffix + || (suffixes.indexOf(Character.toLowerCase(c)) >= 0 // valid + // suffix + && pos == text.length() // at end of text and + && suffixPos == -1) // there isn't already one + )) + { + // don't process + e.consume(); + } + } + }); + } + + private boolean isMemoryMaxTextFieldValid() + { + return MemorySetting + .isValidMemoryString(jvmMemoryMaxTextField.getText()); + } + + private void validateMemoryMaxTextField() + { + if (isMemoryMaxTextFieldValid()) + { + jvmMemoryMaxTextField.setBackground(Color.WHITE); + } + else + { + jvmMemoryMaxTextField.setBackground(Color.PINK); + } + } + + private void setMemoryPercentDisplay() + { + jvmMemoryPercentDisplay + .setText(jvmMemoryPercentSlider.getValue() + "%"); + setExampleMemoryLabel(); + } + + private void setExampleMemoryLabel() + { + boolean selected = customiseMemorySetting.isSelected(); + int jvmmempc = jvmMemoryPercentSlider.getValue(); + String jvmmemmax = jvmMemoryMaxTextField.getText(); + + long mem; + if (selected && (0 <= jvmmempc && jvmmempc <= 100) + && MemorySetting.isValidMemoryString(jvmmemmax)) + { + mem = MemorySetting.getMemorySetting(jvmmemmax, + String.valueOf(jvmmempc), false, true); + } + else + { + mem = MemorySetting.getMemorySetting(null, null, false, true); + } + exampleMemoryLabel.setText(MemorySetting.memoryLongToString(mem)); + String message = MemorySetting.getAdjustmentMessage(); + exampleMemoryMessageTextArea.setText( + MessageManager.getString("label.adjustments_for_this_computer") + + ": " + + (message == null + ? MessageManager.getString("label.none") + : message)); + } + + private void memoryOptionsSetEnabled() + { + boolean enabled = customiseMemorySetting.isSelected(); + // leave exampleMemoryLabel enabled always + maxMemoryLabel.setEnabled(enabled); + jvmMemoryPercentLabel.setEnabled(enabled); + jvmMemoryPercentSlider.setEnabled(enabled); + jvmMemoryPercentDisplay.setEnabled(enabled); + jvmMemoryMaxLabel.setEnabled(enabled); + jvmMemoryMaxTextField.setEnabled(enabled); + setExampleMemoryLabel(); + } + /** * Initialises the Backups tabbed panel. * diff --git a/src/jalview/util/ChannelProperties.java b/src/jalview/util/ChannelProperties.java index 109eaa5..cf3b190 100644 --- a/src/jalview/util/ChannelProperties.java +++ b/src/jalview/util/ChannelProperties.java @@ -13,14 +13,12 @@ import java.util.Properties; import javax.swing.ImageIcon; -import jalview.bin.Cache; - public class ChannelProperties { - private static final String CHANNEL_PROPERTIES_FILENAME = "/channel.props"; + public static final String CHANNEL_PROPERTIES_FILENAME = "channel.props"; - private static final Properties channelProps; + private static Properties channelProps; private static final Properties defaultProps; @@ -60,11 +58,11 @@ public class ChannelProperties // load channel_properties Properties tryChannelProps = new Properties(); URL channelPropsURL = ChannelProperties.class - .getResource(CHANNEL_PROPERTIES_FILENAME); + .getResource("/" + CHANNEL_PROPERTIES_FILENAME); if (channelPropsURL == null) { // complete failure of channel_properties, set all properties to defaults - System.err.println("Failed to find '" + CHANNEL_PROPERTIES_FILENAME + System.err.println("Failed to find '/" + CHANNEL_PROPERTIES_FILENAME + "' file at '" + (channelPropsURL == null ? "null" : channelPropsURL.toString()) @@ -80,7 +78,7 @@ public class ChannelProperties channelPropsIS.close(); } catch (IOException e) { - Cache.log.warn(e.getMessage()); + System.err.println(e.getMessage()); // return false; } } @@ -113,7 +111,9 @@ public class ChannelProperties Image logo = null; String path = defaultProps.getProperty("logo." + size); URL imageURL = ChannelProperties.class.getResource(path); - logo = new ImageIcon(imageURL).getImage(); + ImageIcon imgIcon = imageURL == null ? null + : new ImageIcon(imageURL); + logo = imgIcon == null ? null : imgIcon.getImage(); if (logo != null) { iconList.add(logo); @@ -122,6 +122,17 @@ public class ChannelProperties } } + public static void loadProps(InputStream is) + { + try + { + channelProps.load(is); + } catch (IOException e) + { + System.err.println(e.getMessage()); + } + } + private static Properties channelProps() { return channelProps; @@ -219,7 +230,8 @@ public class ChannelProperties } URL imageURL = ChannelProperties.class.getResource(path); - img = new ImageIcon(imageURL).getImage(); + ImageIcon imgIcon = imageURL == null ? null : new ImageIcon(imageURL); + img = imgIcon == null ? null : imgIcon.getImage(); if (img == null) { System.err.println( diff --git a/src/jalview/util/StringUtils.java b/src/jalview/util/StringUtils.java index 1f114a8..d758395 100644 --- a/src/jalview/util/StringUtils.java +++ b/src/jalview/util/StringUtils.java @@ -446,7 +446,7 @@ public class StringUtils { text = text.substring(0, endTag); } - + if (startTag == -1 && (text.contains("<") || text.contains(">"))) { text = text.replaceAll("<", "<"); @@ -456,8 +456,8 @@ public class StringUtils } /** - * Answers the input string with any occurrences of the 'encodeable' characters - * replaced by their URL encoding + * Answers the input string with any occurrences of the 'encodeable' + * characters replaced by their URL encoding * * @param s * @param encodable @@ -568,4 +568,18 @@ public class StringUtils } return enc; } + + public static int firstCharPosIgnoreCase(String text, String chars) + { + int min = text.length() + 1; + for (char c : chars.toLowerCase().toCharArray()) + { + int i = text.toLowerCase().indexOf(c); + if (0 <= i && i < min) + { + min = i; + } + } + return min < text.length() + 1 ? min : -1; + } } -- 1.7.10.2