Merge branch 'bug/JAL-3806_mappingCoversSequence_for2112' into develop bug/JAL-3806_without_shared_DS_for_2_11_2
authorJim Procter <j.procter@dundee.ac.uk>
Fri, 4 Feb 2022 14:34:20 +0000 (14:34 +0000)
committerJim Procter <j.procter@dundee.ac.uk>
Fri, 4 Feb 2022 14:34:20 +0000 (14:34 +0000)
Combines patches for JAL-3806 prior to redaction of shared CDS dataset sequences with patches for JAL-3673
 Conflicts:
src/jalview/datamodel/AlignedCodonFrame.java
src/jalview/util/MappingUtils.java
test/jalview/util/MappingUtilsTest.java

59 files changed:
build.gradle
getdown/lib/FJVL_VERSION
getdown/lib/JVL_VERSION
getdown/lib/getdown-core.jar
getdown/lib/getdown-launcher-local.jar
getdown/lib/getdown-launcher.jar
getdown/src/getdown/ant/pom.xml
getdown/src/getdown/core/pom.xml
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java
getdown/src/getdown/core/src/main/java/jalview/bin/MemorySetting.java
getdown/src/getdown/core/src/main/java/jalview/util/ChannelProperties.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/jalview/util/LaunchUtils.java [new file with mode: 0644]
getdown/src/getdown/core/src/main/java/jalview/util/StringUtils.java [new file with mode: 0644]
getdown/src/getdown/launcher/pom.xml
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java
getdown/src/getdown/mvn_cmd
getdown/src/getdown/pom.xml
gradle.properties
help/help/html/releases.html
j11lib/getdown-core.jar
j11lib/log4j-1.2-api-2.17.1.jar [moved from j11lib/log4j-1.2-api-2.17.0.jar with 76% similarity]
j11lib/log4j-api-2.17.1.jar [moved from j11lib/log4j-api-2.17.0.jar with 70% similarity]
j11lib/log4j-core-2.17.1.jar [moved from j8lib/log4j-core-2.17.0.jar with 78% similarity]
j11lib/log4j-slf4j18-impl-2.17.1.jar [moved from j8lib/log4j-slf4j18-impl-2.17.0.jar with 69% similarity]
j8lib/getdown-core.jar
j8lib/log4j-1.2-api-2.17.1.jar [moved from j8lib/log4j-1.2-api-2.17.0.jar with 76% similarity]
j8lib/log4j-api-2.17.1.jar [moved from j8lib/log4j-api-2.17.0.jar with 70% similarity]
j8lib/log4j-core-2.17.1.jar [moved from j11lib/log4j-core-2.17.0.jar with 78% similarity]
j8lib/log4j-slf4j18-impl-2.17.1.jar [moved from j11lib/log4j-slf4j18-impl-2.17.0.jar with 69% similarity]
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/analysis/AlignmentUtils.java
src/jalview/bin/Launcher.java
src/jalview/bin/MemorySetting.java
src/jalview/datamodel/AlignedCodonFrame.java
src/jalview/datamodel/SearchResults.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/jbgui/GPreferences.java
src/jalview/util/ChannelProperties.java
src/jalview/util/LaunchUtils.java [new file with mode: 0644]
src/jalview/util/MappingUtils.java
src/jalview/util/StringUtils.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
test/jalview/bin/CommandLineOperations.java
test/jalview/bin/HiDPISettingTest2.java
test/jalview/datamodel/AlignedCodonFrameTest.java
test/jalview/util/MapListTest.java
test/jalview/util/MappingUtilsTest.java
utils/channels/default/resources/channel.props
utils/install4j/auto_file_associations-i4j8.pl
utils/install4j/file_associations_auto-Info_plist.xml
utils/install4j/file_associations_auto-install4j8.xml
utils/install4j/install4j8_template.install4j
utils/install4j/jvl_file.icns [new file with mode: 0644]
utils/install4j/jvl_file.ico [new file with mode: 0644]
utils/install4j/jvl_file.png [new file with mode: 0644]
utils/install4j/jvl_file.svg [new file with mode: 0644]

index 17407a9..34a3461 100644 (file)
@@ -103,6 +103,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"
@@ -1511,6 +1512,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) {
@@ -1684,6 +1691,7 @@ task getdownWebsite() {
       from launchJvl
       from getdownLauncher
       from "${getdownWebsiteDir}/${getdown_build_properties}"
+      from "${getdownWebsiteDir}/${channel_props}"
       if (file(getdownLauncher).getName() != getdown_launcher) {
         rename(file(getdownLauncher).getName(), getdown_launcher)
       }
index df225c8..2cac91c 100644 (file)
@@ -1 +1 @@
-1.8.3-1.2.10_FJVL
+1.8.3-1.2.11_FJVL
index 6f6eed4..99f8359 100644 (file)
@@ -1 +1 @@
-1.8.3-1.2.10_JVL
+1.8.3-1.2.11_JVL
index dadce6e..4edf9b0 100644 (file)
Binary files a/getdown/lib/getdown-core.jar and b/getdown/lib/getdown-core.jar differ
index 88036f9..500b38b 100644 (file)
Binary files a/getdown/lib/getdown-launcher-local.jar and b/getdown/lib/getdown-launcher-local.jar differ
index 4e2a98c..2ead1c7 100644 (file)
Binary files a/getdown/lib/getdown-launcher.jar and b/getdown/lib/getdown-launcher.jar differ
index 9b26d50..e67984c 100644 (file)
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.threerings.getdown</groupId>
     <artifactId>getdown</artifactId>
-    <version>1.8.3-1.2.10_FJVL</version>
+    <version>1.8.3-1.2.11_FJVL</version>
   </parent>
 
   <artifactId>getdown-ant</artifactId>
index eb6f388..f909444 100644 (file)
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.threerings.getdown</groupId>
     <artifactId>getdown</artifactId>
-    <version>1.8.3-1.2.10_FJVL</version>
+    <version>1.8.3-1.2.11_FJVL</version>
   </parent>
 
   <artifactId>getdown-core</artifactId>
index 2022750..684844a 100644 (file)
@@ -31,7 +31,7 @@ import java.util.zip.GZIPInputStream;
 
 import jalview.bin.HiDPISetting;
 import jalview.bin.MemorySetting;
-//import com.install4j.api.launcher.Variables;
+import jalview.util.LaunchUtils;
 
 import com.threerings.getdown.util.*;
 // avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+
@@ -1181,6 +1181,17 @@ public class Application
             continue;
           }
         }
+
+        // use saved preferences if no cmdline args
+        LaunchUtils.loadChannelProps(getAppDir());
+        if (LaunchUtils.getBooleanUserPreference(MemorySetting.CUSTOMISED_SETTINGS)) {
+          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;
index 5d7f14c..55e304d 100644 (file)
@@ -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 (file)
index 0000000..40f6110
--- /dev/null
@@ -0,0 +1,288 @@
+package jalview.util;
+
+import java.awt.Image;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+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<String, Image> imageMap = new HashMap<String, Image>();
+
+  private static Map<String, URL> urlMap = new HashMap<String, URL>();
+
+  private static final ArrayList<Image> 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<Image>();
+    List<String> 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);
+        }
+      }
+    }
+  }
+
+  protected static void loadProps(File dir)
+  {
+    File channelPropsFile = new File(dir, CHANNEL_PROPERTIES_FILENAME);
+    if (channelPropsFile.exists())
+    {
+      try
+      {
+        InputStream is = new FileInputStream(channelPropsFile);
+        channelProps.load(is);
+      } catch (FileNotFoundException e)
+      {
+        System.err.println(e.getMessage());
+      } catch (IOException e)
+      {
+        System.err.println(e.getMessage());
+      }
+    }
+  }
+
+  private static Properties channelProps()
+  {
+    return channelProps;
+  }
+
+  private static Map<String, Image> imageMap()
+  {
+    return imageMap;
+  }
+
+  private static Map<String, URL> 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<Image> getIconList()
+  {
+    return iconList;
+  }
+}
diff --git a/getdown/src/getdown/core/src/main/java/jalview/util/LaunchUtils.java b/getdown/src/getdown/core/src/main/java/jalview/util/LaunchUtils.java
new file mode 100644 (file)
index 0000000..3302dba
--- /dev/null
@@ -0,0 +1,56 @@
+package jalview.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+
+public class LaunchUtils
+{
+
+  public static void loadChannelProps(File dir)
+  {
+    ChannelProperties.loadProps(dir);
+  }
+
+  private static Properties userPreferences = null;
+
+  public static String getUserPreference(String key)
+  {
+    if (userPreferences == null)
+    {
+      String channelPrefsFilename = ChannelProperties
+              .getProperty("preferences.filename");
+      if (channelPrefsFilename == null)
+      {
+        return null;
+      }
+      File propertiesFile = new File(System.getProperty("user.home"),
+              channelPrefsFilename);
+      if (!propertiesFile.exists())
+      {
+        return null;
+      }
+      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);
+  }
+
+  public static boolean getBooleanUserPreference(String key)
+  {
+    return Boolean.parseBoolean(getUserPreference(key));
+  }
+}
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 (file)
index 0000000..d758395
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ * 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<String> 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<String> 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("<HTML>");
+    if (startTag > -1)
+    {
+      text = text.substring(startTag + 6);
+      tmp2up = tmp2up.substring(startTag + 6);
+    }
+    // is omission of "<BODY>" intentional here??
+    int endTag = tmp2up.indexOf("</BODY>");
+    if (endTag > -1)
+    {
+      text = text.substring(0, endTag);
+      tmp2up = tmp2up.substring(0, endTag);
+    }
+    endTag = tmp2up.indexOf("</HTML>");
+    if (endTag > -1)
+    {
+      text = text.substring(0, endTag);
+    }
+
+    if (startTag == -1 && (text.contains("<") || text.contains(">")))
+    {
+      text = text.replaceAll("<", "&lt;");
+      text = text.replaceAll(">", "&gt;");
+    }
+    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.
+   * <p>
+   * 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;
+  }
+}
index b5e68f2..5284412 100644 (file)
@@ -4,7 +4,7 @@
   <parent>
     <groupId>com.threerings.getdown</groupId>
     <artifactId>getdown</artifactId>
-    <version>1.8.3-1.2.10_FJVL</version>
+    <version>1.8.3-1.2.11_FJVL</version>
   </parent>
 
   <artifactId>getdown-launcher</artifactId>
index 2178273..5d697df 100644 (file)
@@ -40,6 +40,12 @@ public final class ProxyPanel extends JPanel implements ActionListener
         _getdown = getdown;
         _msgs = msgs;
 
+        String[] hostPortAuthUser = ProxyUtil.jalviewProxyProperties(getdown._app);
+        String host = hostPortAuthUser[0];
+        String port = hostPortAuthUser[1];
+        boolean proxyAuth = Boolean.parseBoolean(hostPortAuthUser[2]);
+        String username = hostPortAuthUser[3];
+
         setLayout(new VGroupLayout());
         setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
         add(new SaneLabelField(get("m.configure_proxy")));
@@ -48,11 +54,13 @@ public final class ProxyPanel extends JPanel implements ActionListener
         JPanel row = new JPanel(new GridLayout());
         row.add(new SaneLabelField(get("m.proxy_host")), BorderLayout.WEST);
         row.add(_host = new SaneTextField());
+        _host.setText(host);
         add(row);
 
         row = new JPanel(new GridLayout());
         row.add(new SaneLabelField(get("m.proxy_port")), BorderLayout.WEST);
         row.add(_port = new SaneTextField());
+        _port.setText(port);
         add(row);
 
         add(new Spacer(5, 5));
@@ -60,20 +68,22 @@ public final class ProxyPanel extends JPanel implements ActionListener
         row = new JPanel(new GridLayout());
         row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST);
         _useAuth = new JCheckBox();
+        _useAuth.setSelected(proxyAuth);
         row.add(_useAuth);
         add(row);
 
         row = new JPanel(new GridLayout());
         row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST);
         _username = new SaneTextField();
-        _username.setEnabled(false);
+        _username.setText(username);
+        _username.setEnabled(_useAuth.isSelected());
         row.add(_username);
         add(row);
 
         row = new JPanel(new GridLayout());
         row.add(new SaneLabelField(get("m.proxy_password")), BorderLayout.WEST);
         _password = new SanePasswordField();
-        _password.setEnabled(false);
+        _password.setEnabled(_useAuth.isSelected());
         row.add(_password);
         add(row);
 
index a36b5fa..cb51ca4 100644 (file)
@@ -6,8 +6,10 @@
 package com.threerings.getdown.launcher;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintStream;
 import java.net.Authenticator;
 import java.net.HttpURLConnection;
@@ -19,6 +21,8 @@ import java.net.URLConnection;
 import java.util.Iterator;
 import java.util.ServiceLoader;
 
+import jalview.util.LaunchUtils;
+
 import ca.beq.util.win32.registry.RegistryKey;
 import ca.beq.util.win32.registry.RegistryValue;
 import ca.beq.util.win32.registry.RootKey;
@@ -88,15 +92,43 @@ public class ProxyUtil {
             port = hostPort[1];
         }
 
+        // look in jalview_properties
+        String[] hostPortAuthUser = jalviewProxyProperties(app);
+        host = hostPortAuthUser[0];
+        port = hostPortAuthUser[1];
+        boolean proxyAuth = Boolean.parseBoolean(hostPortAuthUser[2]);
+        String username = hostPortAuthUser[3];
+
         if (StringUtil.isBlank(host)) {
             return false;
         }
 
         // yay, we found a proxy configuration, configure it in the app
-        initProxy(app, host, port, null, null);
+        initProxy(app, host, port, username, null);
         return true;
     }
 
+    public static String[] jalviewProxyProperties(Application app) {
+      String host = null;
+      String port = null;
+      boolean proxyAuth = false;
+      String username = null;
+      LaunchUtils.loadChannelProps(app.getAppDir());
+      if (LaunchUtils.getBooleanUserPreference("USE_PROXY")) {
+        host = LaunchUtils.getUserPreference("PROXY_SERVER_HTTPS");
+        port = LaunchUtils.getUserPreference("PROXY_PORT_HTTPS");
+        if (StringUtil.isBlank(host)) {
+          host = LaunchUtils.getUserPreference("PROXY_SERVER");
+          port = LaunchUtils.getUserPreference("PROXY_PORT");
+        }
+        proxyAuth = LaunchUtils.getBooleanUserPreference("PROXY_AUTH");
+        if (proxyAuth) {
+          username = LaunchUtils.getUserPreference("PROXY_AUTH_USERNAME");
+        }
+      }
+      return new String[]{ host, port, String.valueOf(proxyAuth), username };
+    }
+
     public static boolean canLoadWithoutProxy (URL rurl)
     {
         log.info("Testing whether proxy is needed, via: " + rurl);
@@ -179,6 +211,7 @@ public class ProxyUtil {
     public static void initProxy (Application app, String host, String port,
                                   String username, String password)
     {
+System.out.println("**** initProxy(app, '"+host+"', "+port+", '"+username+"', "+(password==null?"null":"*x"+password.length())+")");
         // check whether we have saved proxy credentials
         String appDir = app.getAppDir().getAbsolutePath();
         ServiceLoader<ProxyAuth> loader = ServiceLoader.load(ProxyAuth.class);
index 65e5fb9..4ffb086 100755 (executable)
@@ -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
index 7a0fd27..61b1440 100644 (file)
@@ -10,7 +10,7 @@
   <groupId>com.threerings.getdown</groupId>
   <artifactId>getdown</artifactId>
   <packaging>pom</packaging>
-  <version>1.8.3-1.2.10_FJVL</version>
+  <version>1.8.3-1.2.11_FJVL</version>
 
   <name>getdown</name>
   <description>An application installer and updater.</description>
index f0c0d9e..21360df 100644 (file)
@@ -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 = ...
index 3255f93..9fe5da5 100755 (executable)
@@ -74,6 +74,9 @@ li:before {
       <td>
         <ul>
           <li>
+            TO DOCUMENT: JAL-3700,JAL-3751,JAL-3763
+          </li>
+          <li>
             <!-- JAL-3915 -->Removed RNAview checkbox and logic from
             Structure Preferences
           </li>
index dadce6e..4edf9b0 100644 (file)
Binary files a/j11lib/getdown-core.jar and b/j11lib/getdown-core.jar differ
similarity index 76%
rename from j11lib/log4j-1.2-api-2.17.0.jar
rename to j11lib/log4j-1.2-api-2.17.1.jar
index a361ddc..ef4f08d 100644 (file)
Binary files a/j11lib/log4j-1.2-api-2.17.0.jar and b/j11lib/log4j-1.2-api-2.17.1.jar differ
similarity index 70%
rename from j11lib/log4j-api-2.17.0.jar
rename to j11lib/log4j-api-2.17.1.jar
index 77af535..1aae243 100644 (file)
Binary files a/j11lib/log4j-api-2.17.0.jar and b/j11lib/log4j-api-2.17.1.jar differ
similarity index 78%
rename from j8lib/log4j-core-2.17.0.jar
rename to j11lib/log4j-core-2.17.1.jar
index 256ff3d..4682527 100644 (file)
Binary files a/j8lib/log4j-core-2.17.0.jar and b/j11lib/log4j-core-2.17.1.jar differ
similarity index 69%
rename from j8lib/log4j-slf4j18-impl-2.17.0.jar
rename to j11lib/log4j-slf4j18-impl-2.17.1.jar
index 43b077b..a2bce46 100644 (file)
Binary files a/j8lib/log4j-slf4j18-impl-2.17.0.jar and b/j11lib/log4j-slf4j18-impl-2.17.1.jar differ
index dadce6e..4edf9b0 100644 (file)
Binary files a/j8lib/getdown-core.jar and b/j8lib/getdown-core.jar differ
similarity index 76%
rename from j8lib/log4j-1.2-api-2.17.0.jar
rename to j8lib/log4j-1.2-api-2.17.1.jar
index a361ddc..ef4f08d 100644 (file)
Binary files a/j8lib/log4j-1.2-api-2.17.0.jar and b/j8lib/log4j-1.2-api-2.17.1.jar differ
similarity index 70%
rename from j8lib/log4j-api-2.17.0.jar
rename to j8lib/log4j-api-2.17.1.jar
index 77af535..1aae243 100644 (file)
Binary files a/j8lib/log4j-api-2.17.0.jar and b/j8lib/log4j-api-2.17.1.jar differ
similarity index 78%
rename from j11lib/log4j-core-2.17.0.jar
rename to j8lib/log4j-core-2.17.1.jar
index 256ff3d..4682527 100644 (file)
Binary files a/j11lib/log4j-core-2.17.0.jar and b/j8lib/log4j-core-2.17.1.jar differ
similarity index 69%
rename from j11lib/log4j-slf4j18-impl-2.17.0.jar
rename to j8lib/log4j-slf4j18-impl-2.17.1.jar
index 43b077b..a2bce46 100644 (file)
Binary files a/j11lib/log4j-slf4j18-impl-2.17.0.jar and b/j8lib/log4j-slf4j18-impl-2.17.1.jar differ
index 9e492b2..b448b4c 100644 (file)
@@ -1399,3 +1399,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).<br>Jalview will always try and reserve 512MB for the OS and at least 512MB for itself.
index 411643d..49e05e3 100644 (file)
@@ -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).<br>Jalview siempre intentará reservar 512 MB para el sistema operativo y al menos 512 MB para sí mismo.
index 23c5d64..f95ff73 100644 (file)
@@ -22,6 +22,23 @@ package jalview.analysis;
 
 import java.util.Locale;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import jalview.bin.Cache;
 import jalview.commands.RemoveGapColCommand;
 import jalview.datamodel.AlignedCodon;
 import jalview.datamodel.AlignedCodonFrame;
@@ -46,22 +63,6 @@ import jalview.util.IntRangeComparator;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
 /**
  * grab bag of useful alignment manipulation operations Expect these to be
  * refactored elsewhere at some point.
@@ -1997,45 +1998,31 @@ public class AlignmentUtils
 
     SequenceI newSeq = null;
 
-    final MapList maplist = mapping.getMap();
-    if (maplist.isContiguous() && maplist.isFromForwardStrand())
-    {
-      /*
-       * just a subsequence, keep same dataset sequence
-       */
-      int start = maplist.getFromLowest();
-      int end = maplist.getFromHighest();
-      newSeq = seq.getSubSequence(start - 1, end);
-      newSeq.setName(seqId);
-    }
-    else
-    {
-      /*
-       * construct by splicing mapped from ranges
-       */
-      char[] seqChars = seq.getSequence();
-      List<int[]> fromRanges = maplist.getFromRanges();
-      int cdsWidth = MappingUtils.getLength(fromRanges);
-      char[] newSeqChars = new char[cdsWidth];
+    /*
+     * construct CDS sequence by splicing mapped from ranges
+     */
+    char[] seqChars = seq.getSequence();
+    List<int[]> fromRanges = mapping.getMap().getFromRanges();
+    int cdsWidth = MappingUtils.getLength(fromRanges);
+    char[] newSeqChars = new char[cdsWidth];
 
-      int newPos = 0;
-      for (int[] range : fromRanges)
+    int newPos = 0;
+    for (int[] range : fromRanges)
+    {
+      if (range[0] <= range[1])
       {
-        if (range[0] <= range[1])
-        {
-          // forward strand mapping - just copy the range
-          int length = range[1] - range[0] + 1;
-          System.arraycopy(seqChars, range[0] - 1, newSeqChars, newPos,
-                  length);
-          newPos += length;
-        }
-        else
+        // forward strand mapping - just copy the range
+        int length = range[1] - range[0] + 1;
+        System.arraycopy(seqChars, range[0] - 1, newSeqChars, newPos,
+                length);
+        newPos += length;
+      }
+      else
+      {
+        // reverse strand mapping - copy and complement one by one
+        for (int i = range[0]; i >= range[1]; i--)
         {
-          // reverse strand mapping - copy and complement one by one
-          for (int i = range[0]; i >= range[1]; i--)
-          {
-            newSeqChars[newPos++] = Dna.getComplement(seqChars[i - 1]);
-          }
+          newSeqChars[newPos++] = Dna.getComplement(seqChars[i - 1]);
         }
       }
 
@@ -2069,9 +2056,8 @@ public class AlignmentUtils
           }
           else
           {
-            System.err.println(
-                    "JAL-2154 regression: warning - found (and ignnored a duplicate CDS sequence):"
-                            + mtch.toString());
+            Cache.log.error(
+                    "JAL-2154 regression: warning - found (and ignored) a duplicate CDS sequence:" + mtch.toString());
           }
         }
       }
index e13f2dd..1ea17d6 100644 (file)
@@ -29,6 +29,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import jalview.util.ChannelProperties;
+import jalview.util.LaunchUtils;
 
 /**
  * A Launcher class for Jalview. This class is used to launch Jalview from the
@@ -106,6 +107,23 @@ public class Launcher
       }
     }
 
+    // use saved preferences if no cmdline args
+    boolean useCustomisedSettings = LaunchUtils
+            .getBooleanUserPreference(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;
@@ -228,7 +246,6 @@ public class Launcher
       e.printStackTrace();
     }
     // System.exit(0);
-
   }
 
 }
index 52f0c9e..56713b0 100644 (file)
@@ -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,8 +33,6 @@ package jalview.bin;
  * @author bsoares
  *
  */
-import java.util.Locale;
-
 public class MemorySetting
 {
   public static final String MAX_HEAPSIZE_PERCENT_PROPERTY_NAME = "jvmmempc";
@@ -51,13 +51,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
@@ -85,99 +105,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(Locale.ROOT).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);
     }
@@ -187,24 +174,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
@@ -225,10 +212,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
@@ -236,16 +223,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;
         }
@@ -254,19 +241,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();
     }
 
@@ -283,9 +272,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;
     }
 
@@ -301,4 +291,120 @@ public class MemorySetting
     return maxMemLong;
   }
 
-}
\ No newline at end of file
+  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, "%.3f");
+  }
+
+  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;
+  }
+
+}
index eda1a13..b376c80 100644 (file)
  */
 package jalview.datamodel;
 
-import jalview.util.MapList;
-import jalview.util.MappingUtils;
-
 import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.List;
 
+import jalview.util.MapList;
+import jalview.util.MappingUtils;
+
 /**
  * Stores mapping between the columns of a protein alignment and a DNA alignment
  * and a list of individual codon to amino acid mappings between sequences.
@@ -107,7 +107,7 @@ public class AlignedCodonFrame
     {
       return mapping;
     }
-    
+
     /**
      * Returns true if the mapping covers the full length of the given sequence.
      * This allows us to distinguish the CDS that codes for a protein from
@@ -210,7 +210,7 @@ public class AlignedCodonFrame
     /**
      * Adds any regions mapped to or from position {@code pos} in sequence
      * {@code seq} to the given search results
-     * 
+     * Note: recommend first using the .covers(,true,true) to ensure mapping covers both sequences
      * @param seq
      * @param pos
      * @param sr
@@ -398,9 +398,12 @@ public class AlignedCodonFrame
   }
 
   /**
+   * Return the corresponding aligned or dataset dna sequence for given amino
+   * acid sequence, or null if not found. returns the sequence from the first
+   * mapping found that involves the protein sequence.
    * 
-   * @param sequenceRef
-   * @return null or corresponding aaSeq entry for dnaSeq entry
+   * @param aaSeqRef
+   * @return
    */
   public SequenceI getDnaForAaSeq(SequenceI aaSeqRef)
   {
@@ -430,7 +433,8 @@ public class AlignedCodonFrame
 
   /**
    * Add search results for regions in other sequences that translate or are
-   * translated from a particular position in seq
+   * translated from a particular position in seq (which may be an aligned or
+   * dataset sequence)
    * 
    * @param seq
    * @param index
@@ -441,71 +445,17 @@ public class AlignedCodonFrame
   public void markMappedRegion(SequenceI seq, int index,
           SearchResultsI results)
   {
-    int[] codon;
     SequenceI ds = seq.getDatasetSequence();
-    for (SequenceToSequenceMapping ssm : mappings)
+    if (ds == null)
     {
-      if (ssm.covers(seq,true,true))
-      {
-      if ((ssm.fromSeq == seq || ssm.fromSeq == ds))
-      {
-        codon = ssm.mapping.map.locateInTo(index, index);
-        if (codon != null)
-        {
-          for (int i = 0; i < codon.length; i += 2)
-          {
-            results.addResult(ssm.mapping.to, codon[i], codon[i + 1]);
-          }
-        }
-      }
-      else if ((ssm.mapping.to == seq || ssm.mapping.to == ds))
-      {
-        {
-          codon = ssm.mapping.map.locateInFrom(index, index);
-          if (codon != null)
-          {
-            for (int i = 0; i < codon.length; i += 2)
-            {
-              results.addResult(ssm.fromSeq, codon[i], codon[i + 1]);
-            }
-          }
-        }
-      }}
+      ds = seq;
     }
-  }
-
-  /**
-   * Returns the DNA codon positions (base 1) for the given position (base 1) in
-   * a mapped protein sequence, or null if no mapping is found.
-   * 
-   * Intended for use in aligning cDNA to match aligned protein. Only the first
-   * mapping found is returned, so not suitable for use if multiple protein
-   * sequences are mapped to the same cDNA (but aligning cDNA as protein is
-   * ill-defined for this case anyway).
-   * 
-   * @param seq
-   *          the DNA dataset sequence
-   * @param aaPos
-   *          residue position (base 1) in a protein sequence
-   * @return
-   */
-  public int[] getDnaPosition(SequenceI seq, int aaPos)
-  {
-    /*
-     * Adapted from markMappedRegion().
-     */
-    MapList ml = null;
-    int i = 0;
     for (SequenceToSequenceMapping ssm : mappings)
     {
-      if (ssm.fromSeq == seq)
-      {
-        ml = getdnaToProt()[i];
-        break;
+      if (ssm.covers(seq,true,true)) {
+        ssm.markMappedRegion(ds, index, results);
       }
-      i++;
     }
-    return ml == null ? null : ml.locateInFrom(aaPos, aaPos);
   }
 
   /**
@@ -906,7 +856,7 @@ public class AlignedCodonFrame
    * Two AlignedCodonFrame objects are equal if they hold the same ordered list
    * of mappings
    * 
-   * @see SequenceToSequenceMapping#
+   * @see SequenceToSequenceMapping#equals
    */
   @Override
   public boolean equals(Object obj)
@@ -922,4 +872,55 @@ public class AlignedCodonFrame
   {
     return mappings;
   }
+
+  /**
+   * Returns the first mapping found which is between the two given sequences,
+   * and covers the full extent of both.
+   * 
+   * @param seq1
+   * @param seq2
+   * @return
+   */
+  public SequenceToSequenceMapping getCoveringMapping(SequenceI seq1,
+          SequenceI seq2)
+  {
+    for (SequenceToSequenceMapping mapping : mappings)
+    {
+      if (mapping.covers(seq2) && mapping.covers(seq1))
+      {
+        return mapping;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the first mapping found which is between the given dataset sequence
+   * and another, is a triplet mapping (3:1 or 1:3), and covers the full extent
+   * of both sequences involved
+   * 
+   * @param seq
+   * @return
+   */
+  public SequenceToSequenceMapping getCoveringCodonMapping(SequenceI seq)
+  {
+    for (SequenceToSequenceMapping mapping : mappings)
+    {
+      if (mapping.getMapping().getMap().isTripletMap()
+              && mapping.covers(seq))
+      {
+        if (mapping.fromSeq == seq
+                && mapping.covers(mapping.getMapping().getTo()))
+        {
+          return mapping;
+        }
+        else if (mapping.getMapping().getTo() == seq
+                && mapping.covers(mapping.fromSeq))
+        {
+          return mapping;
+        }
+      }
+    }
+    return null;
+  }
 }
index 7c3bba7..5c929fc 100755 (executable)
@@ -349,8 +349,10 @@ public class SearchResults implements SearchResultsI
   }
 
   /**
-   * Two SearchResults are considered equal if they contain the same matches in
-   * the same order.
+   * Two SearchResults are considered equal if they contain the same matches
+   * (Sequence, start position, end position) in the same order
+   * 
+   * @see Match#equals(Object)
    */
   @Override
   public boolean equals(Object obj)
index d1e8e32..7b284a9 100644 (file)
@@ -842,6 +842,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       int[] localRange = mf.getMappedPositions(start, end);
       if (localRange == null)
       {
+        // e.g. variant extending to stop codon so not mappable
         return;
       }
       start = localRange[0];
index d8eee6d..14063ac 100755 (executable)
@@ -54,6 +54,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;
@@ -683,6 +684,11 @@ public class Preferences extends GPreferences
      * Set Backups tab defaults
      */
     loadLastSavedBackupsOptions();
+
+    /*
+     * Set Startup tab defaults
+     */
+
   }
 
   /**
@@ -995,6 +1001,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
index debb2ab..37ba654 100755 (executable)
@@ -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.
@@ -379,6 +383,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<Object> lafCombo = new JComboBox<>();
+
   /**
    * Creates a new GPreferences object.
    */
@@ -440,6 +468,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.
      */
@@ -2200,6 +2231,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
@@ -2217,6 +2261,285 @@ 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);
+    exampleMemoryMessageTextArea.setEnabled(enabled);
+    setExampleMemoryLabel();
+  }
+
   /**
    * Initialises the Backups tabbed panel.
    * 
index 109eaa5..40f6110 100644 (file)
@@ -1,6 +1,9 @@
 package jalview.util;
 
 import java.awt.Image;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
@@ -13,14 +16,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 +61,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 +81,7 @@ public class ChannelProperties
         channelPropsIS.close();
       } catch (IOException e)
       {
-        Cache.log.warn(e.getMessage());
+        System.err.println(e.getMessage());
         // return false;
       }
     }
@@ -113,7 +114,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 +125,25 @@ public class ChannelProperties
     }
   }
 
+  protected static void loadProps(File dir)
+  {
+    File channelPropsFile = new File(dir, CHANNEL_PROPERTIES_FILENAME);
+    if (channelPropsFile.exists())
+    {
+      try
+      {
+        InputStream is = new FileInputStream(channelPropsFile);
+        channelProps.load(is);
+      } catch (FileNotFoundException e)
+      {
+        System.err.println(e.getMessage());
+      } catch (IOException e)
+      {
+        System.err.println(e.getMessage());
+      }
+    }
+  }
+
   private static Properties channelProps()
   {
     return channelProps;
@@ -219,7 +241,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/LaunchUtils.java b/src/jalview/util/LaunchUtils.java
new file mode 100644 (file)
index 0000000..3302dba
--- /dev/null
@@ -0,0 +1,56 @@
+package jalview.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+
+public class LaunchUtils
+{
+
+  public static void loadChannelProps(File dir)
+  {
+    ChannelProperties.loadProps(dir);
+  }
+
+  private static Properties userPreferences = null;
+
+  public static String getUserPreference(String key)
+  {
+    if (userPreferences == null)
+    {
+      String channelPrefsFilename = ChannelProperties
+              .getProperty("preferences.filename");
+      if (channelPrefsFilename == null)
+      {
+        return null;
+      }
+      File propertiesFile = new File(System.getProperty("user.home"),
+              channelPrefsFilename);
+      if (!propertiesFile.exists())
+      {
+        return null;
+      }
+      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);
+  }
+
+  public static boolean getBooleanUserPreference(String key)
+  {
+    return Boolean.parseBoolean(getUserPreference(key));
+  }
+}
index c66fe00..86b7a6d 100644 (file)
@@ -370,6 +370,8 @@ public final class MappingUtils
         int mappedEndResidue = 0;
         for (AlignedCodonFrame acf : codonFrames)
         {
+          // rather than use acf.getCoveringMapping() we iterate through all
+          // mappings to make sure all CDS are selected for a protein
           for (SequenceToSequenceMapping map: acf.getMappings())
           {
           if (map.covers(selected) && map.covers(seq))
@@ -449,20 +451,23 @@ public final class MappingUtils
     {
       for (AlignedCodonFrame acf : mappings)
       {
-        SequenceI mappedSeq = mappingToNucleotide ? acf.getDnaForAaSeq(seq)
-                : acf.getAaForDnaSeq(seq);
-        if (mappedSeq != null)
-        {
           for (SequenceI seq2 : mapTo.getSequences())
           {
-            if (seq2.getDatasetSequence() == mappedSeq)
+            /*
+             * the corresponding peptide / CDS is the one for which there is
+             * a complete ('covering') mapping to 'seq'
+             */
+            SequenceI peptide = mappingToNucleotide ? seq2 : seq;
+            SequenceI cds = mappingToNucleotide ? seq : seq2;
+            SequenceToSequenceMapping s2s = acf.getCoveringMapping(cds,
+                    peptide);
+            if (s2s != null)
             {
               mappedOrder.add(seq2);
               j++;
               break;
             }
           }
-        }
       }
     }
 
@@ -524,7 +529,7 @@ public final class MappingUtils
 
     if (colsel == null)
     {
-      return; // mappedColumns;
+      return; 
     }
 
     char fromGapChar = mapFrom.getAlignment().getGapCharacter();
@@ -548,7 +553,7 @@ public final class MappingUtils
       mapHiddenColumns(regions.next(), codonFrames, newHidden,
               fromSequences, toSequences, fromGapChar);
     }
-    return; // mappedColumns;
+    return; 
   }
 
   /**
@@ -666,7 +671,9 @@ public final class MappingUtils
          */
         for (SequenceI toSeq : toSequences)
         {
-          if (toSeq.getDatasetSequence() == mappedSeq)
+          if (toSeq.getDatasetSequence() == mappedSeq
+                  && mappedStartResidue >= toSeq.getStart()
+                  && mappedEndResidue <= toSeq.getEnd())
           {
             int mappedStartCol = toSeq.findIndex(mappedStartResidue);
             int mappedEndCol = toSeq.findIndex(mappedEndResidue);
index bf5b87a..273f6ec 100644 (file)
@@ -448,7 +448,7 @@ public class StringUtils
     {
       text = text.substring(0, endTag);
     }
-  
+
     if (startTag == -1 && (text.contains("<") || text.contains(">")))
     {
       text = text.replaceAll("<", "&lt;");
@@ -458,8 +458,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
@@ -570,4 +570,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;
+  }
 }
index 853bc3b..eb1030c 100644 (file)
@@ -39,9 +39,9 @@ import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
 import jalview.api.FeaturesDisplayedI;
 import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.MappedFeatures;
-import jalview.datamodel.Mapping;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
@@ -1233,18 +1233,18 @@ public abstract class FeatureRendererModel
      * todo: direct lookup of CDS for peptide and vice-versa; for now,
      * have to search through an unordered list of mappings for a candidate
      */
-    Mapping mapping = null;
+    SequenceToSequenceMapping mapping = null;
     SequenceI mapFrom = null;
 
     for (AlignedCodonFrame acf : mappings)
     {
-      mapping = acf.getMappingForSequence(sequence);
-      if (mapping == null || !mapping.getMap().isTripletMap())
+      mapping = acf.getCoveringCodonMapping(ds);
+      if (mapping == null)
       {
-        continue; // we are only looking for 3:1 or 1:3 mappings
+        continue;
       }
       SearchResultsI sr = new SearchResults();
-      acf.markMappedRegion(ds, pos, sr);
+      mapping.markMappedRegion(ds, pos, sr);
       for (SearchResultMatchI match : sr.getResults())
       {
         int fromRes = match.getStart();
@@ -1297,7 +1297,7 @@ public abstract class FeatureRendererModel
       }
     }
     
-    return new MappedFeatures(mapping, mapFrom, pos, residue, result);
+    return new MappedFeatures(mapping.getMapping(), mapFrom, pos, residue, result);
   }
 
   @Override
index 59fc79d..a539f78 100644 (file)
@@ -54,7 +54,7 @@ public class CommandLineOperations
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
-  private static final int TEST_TIMEOUT = 9000; // Note longer timeout needed
+  private static final int TEST_TIMEOUT = 10500; // Note longer timeout needed
                                                 // on
                                                 // full test run than on
                                                 // individual tests
index 71d13d0..470ec35 100644 (file)
@@ -162,7 +162,7 @@ public class HiDPISettingTest2
   @Test(groups = { "Functional" }, dataProvider = "hidpiScaleArguments")
   public void testHiDPISettings(int scale)
   {
-    if (Platform.isLinux())
+    if (!Platform.isLinux())
     {
       throw new SkipException(
               "Not linux platform, not testing actual scaling with "
index fb4073a..337ac1a 100644 (file)
@@ -22,20 +22,22 @@ package jalview.datamodel;
 
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNotNull;
 import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
 
-import jalview.gui.JvOptionPane;
-import jalview.util.MapList;
-
 import java.util.Arrays;
 import java.util.List;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
+import jalview.gui.JvOptionPane;
+import jalview.util.MapList;
+
 public class AlignedCodonFrameTest
 {
 
@@ -98,52 +100,67 @@ public class AlignedCodonFrameTest
   public void testGetMappedRegion()
   {
     // introns lower case, exons upper case
-    final Sequence seq1 = new Sequence("Seq1", "c-G-TA-gC-gT-T");
-    seq1.createDatasetSequence();
-    final Sequence seq2 = new Sequence("Seq2", "-TA-gG-Gg-CG-a");
-    seq2.createDatasetSequence();
+    final Sequence dna1 = new Sequence("Seq1/10-18", "c-G-TA-gC-gT-T");
+    dna1.createDatasetSequence();
+    final Sequence dna2 = new Sequence("Seq2/20-28", "-TA-gG-Gg-CG-a");
+    dna2.createDatasetSequence();
 
-    final Sequence aseq1 = new Sequence("Seq1", "-P-R");
-    aseq1.createDatasetSequence();
-    final Sequence aseq2 = new Sequence("Seq2", "-LY-Q");
-    aseq2.createDatasetSequence();
+    final Sequence pep1 = new Sequence("Seq1/3-4", "-P-R");
+    pep1.createDatasetSequence();
+    final Sequence pep2 = new Sequence("Seq2/7-9", "-LY-Q");
+    pep2.createDatasetSequence();
 
     /*
      * First with no mappings
      */
     AlignedCodonFrame acf = new AlignedCodonFrame();
 
-    assertNull(acf.getMappedRegion(seq1, aseq1, 1));
+    assertNull(acf.getMappedRegion(dna1, pep1, 3));
 
     /*
      * Set up the mappings for the exons (upper-case bases)
      * Note residue Q is unmapped
      */
-    MapList map = new MapList(new int[] { 2, 4, 6, 6, 8, 9 }, new int[] {
-        1, 2 }, 3, 1);
-    acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
-    map = new MapList(new int[] { 1, 2, 4, 5, 7, 8 }, new int[] { 1, 2 },
+    MapList map1 = new MapList(new int[] { 11, 13, 15, 15, 17, 18 }, new int[] {
+        3, 4 }, 3, 1);
+    acf.addMap(dna1.getDatasetSequence(), pep1.getDatasetSequence(), map1);
+    MapList map2 = new MapList(new int[] { 20, 21, 23, 24, 26, 27 }, new int[] { 7, 9 },
             3, 1);
-    acf.addMap(seq2.getDatasetSequence(), aseq2.getDatasetSequence(), map);
+    acf.addMap(dna2.getDatasetSequence(), pep2.getDatasetSequence(), map2);
 
-    assertArrayEquals(new int[] { 2, 4 },
-            acf.getMappedRegion(seq1, aseq1, 1));
-    assertArrayEquals(new int[] { 6, 6, 8, 9 },
-            acf.getMappedRegion(seq1, aseq1, 2));
-    assertArrayEquals(new int[] { 1, 2, 4, 4 },
-            acf.getMappedRegion(seq2, aseq2, 1));
-    assertArrayEquals(new int[] { 5, 5, 7, 8 },
-            acf.getMappedRegion(seq2, aseq2, 2));
+    /*
+     * get codon positions for peptide position
+     */
+    assertArrayEquals(new int[] { 11, 13 },
+            acf.getMappedRegion(dna1, pep1, 3));
+    assertArrayEquals(new int[] { 15, 15, 17, 18 },
+            acf.getMappedRegion(dna1, pep1, 4));
+    assertArrayEquals(new int[] { 20, 21, 23, 23 },
+            acf.getMappedRegion(dna2, pep2, 7));
+    assertArrayEquals(new int[] { 24, 24, 26, 27 },
+            acf.getMappedRegion(dna2, pep2, 8));
 
     /*
-     * No mapping from seq2 to Q
+     * No mapping from dna2 to Q
      */
-    assertNull(acf.getMappedRegion(seq2, aseq2, 3));
+    assertNull(acf.getMappedRegion(dna2, pep2, 9));
 
     /*
-     * No mapping from sequence 1 to sequence 2
+     * No mapping from dna1 to pep2
      */
-    assertNull(acf.getMappedRegion(seq1, aseq2, 1));
+    assertNull(acf.getMappedRegion(dna1, pep2, 7));
+    
+    /*
+     * get peptide position for codon position
+     */
+    assertArrayEquals(new int[] { 3, 3 },
+            acf.getMappedRegion(pep1, dna1, 11));
+    assertArrayEquals(new int[] { 3, 3 },
+            acf.getMappedRegion(pep1, dna1, 12));
+    assertArrayEquals(new int[] { 3, 3 },
+            acf.getMappedRegion(pep1, dna1, 13));
+    assertNull(acf.getMappedRegion(pep1, dna1, 14)); // intron base, not mapped
+
   }
 
   @Test(groups = { "Functional" })
@@ -486,4 +503,212 @@ public class AlignedCodonFrameTest
     assertEquals(1, acf.getMappingsFromSequence(seq1).size());
     assertSame(before, acf.getMappingsFromSequence(seq1).get(0));
   }
+  
+  @Test(groups = { "Functional" })
+  public void testGetCoveringMapping()
+  {
+    SequenceI dna = new Sequence("dna", "acttcaATGGCGGACtaattt");
+    SequenceI cds = new Sequence("cds/7-15", "ATGGCGGAC");
+    cds.setDatasetSequence(dna);
+    SequenceI pep = new Sequence("pep", "MAD");
+    
+    /*
+     * with null argument or no mappings
+     */
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    assertNull(acf.getCoveringMapping(null,  null));
+    assertNull(acf.getCoveringMapping(dna,  null));
+    assertNull(acf.getCoveringMapping(null,  pep));
+    assertNull(acf.getCoveringMapping(dna,  pep));
+
+    /*
+     * with a non-covering mapping e.g. overlapping exon
+     */
+    MapList map = new MapList(new int[] { 7, 9 }, new int[] {
+        1, 1 }, 3, 1);
+    acf.addMap(dna, pep, map);
+    assertNull(acf.getCoveringMapping(dna,  pep));
+    
+    acf = new AlignedCodonFrame();
+    MapList map2 = new MapList(new int[] { 13, 18 }, new int[] {
+        2, 2 }, 3, 1);
+    acf.addMap(dna, pep, map2);
+    assertNull(acf.getCoveringMapping(dna,  pep));
+    
+    /*
+     * with a covering mapping from CDS (dataset) to protein
+     */
+    acf = new AlignedCodonFrame();
+    MapList map3 = new MapList(new int[] { 7, 15 }, new int[] {
+        1, 3 }, 3, 1);
+    acf.addMap(dna, pep, map3);
+    assertNull(acf.getCoveringMapping(dna,  pep));
+    SequenceToSequenceMapping mapping = acf.getCoveringMapping(cds,  pep);
+    assertNotNull(mapping);
+    
+    /*
+     * with a mapping that extends to stop codon
+     */
+    acf = new AlignedCodonFrame();
+    MapList map4 = new MapList(new int[] { 7, 18 }, new int[] {
+        1, 3 }, 3, 1);
+    acf.addMap(dna, pep, map4);
+    assertNull(acf.getCoveringMapping(dna,  pep));
+    assertNull(acf.getCoveringMapping(cds,  pep));
+    SequenceI cds2 = new Sequence("cds/7-18", "ATGGCGGACtaa");
+    cds2.setDatasetSequence(dna);
+    mapping = acf.getCoveringMapping(cds2,  pep);
+    assertNotNull(mapping);
+  }
+
+  /**
+   * Test the method that adds mapped positions to SearchResults
+   */
+  @Test(groups = { "Functional" })
+  public void testMarkMappedRegion()
+  {
+    // introns lower case, exons upper case
+    final Sequence dna1 = new Sequence("Seq1/10-18", "c-G-TA-gC-gT-T");
+    dna1.createDatasetSequence();
+    final Sequence dna2 = new Sequence("Seq2/20-28", "-TA-gG-Gg-CG-a");
+    dna2.createDatasetSequence();
+  
+    final Sequence pep1 = new Sequence("Seq1/3-4", "-P-R");
+    pep1.createDatasetSequence();
+    final Sequence pep2 = new Sequence("Seq2/7-9", "-LY-Q");
+    pep2.createDatasetSequence();
+  
+    /*
+     * First with no mappings
+     */
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    SearchResults sr = new SearchResults();
+    acf.markMappedRegion(dna1, 12, sr);
+    assertTrue(sr.isEmpty());
+  
+    /*
+     * Set up the mappings for the exons (upper-case bases)
+     * Note residue Q is unmapped
+     */
+    MapList map1 = new MapList(new int[] { 11, 13, 15, 15, 17, 18 }, new int[] {
+        3, 4 }, 3, 1);
+    acf.addMap(dna1.getDatasetSequence(), pep1.getDatasetSequence(), map1);
+    MapList map2 = new MapList(new int[] { 20, 21, 23, 24, 26, 27 }, new int[] { 7, 8 },
+            3, 1);
+    acf.addMap(dna2.getDatasetSequence(), pep2.getDatasetSequence(), map2);
+    
+    /*
+     * intron bases are not mapped
+     */
+    acf.markMappedRegion(dna1, 10, sr);
+    assertTrue(sr.isEmpty());
+  
+    /*
+     * Q is not mapped
+     */
+    acf.markMappedRegion(pep2, 9, sr);
+    assertTrue(sr.isEmpty());
+
+    /*
+     * mark peptide position for exon position (of aligned sequence)
+     */
+    acf.markMappedRegion(dna1, 11, sr);
+    SearchResults expected = new SearchResults();
+    expected.addResult(pep1.getDatasetSequence(),  3, 3);
+    assertEquals(sr, expected);
+
+    /*
+     * mark peptide position for exon position of dataset sequence - same result
+     */
+    sr = new SearchResults();
+    acf.markMappedRegion(dna1.getDatasetSequence(), 11, sr);
+    assertEquals(sr, expected);
+    
+    /*
+     * marking the same position a second time should not create a duplicate match
+     */
+    acf.markMappedRegion(dna1.getDatasetSequence(), 12, sr);
+    assertEquals(sr, expected);
+    
+    /*
+     * mark exon positions for peptide position (of aligned sequence)
+     */
+    sr = new SearchResults();
+    acf.markMappedRegion(pep2, 7, sr); // codon positions 20, 21, 23
+    expected = new SearchResults();
+    expected.addResult(dna2.getDatasetSequence(),  20, 21);
+    expected.addResult(dna2.getDatasetSequence(),  23, 23);
+    assertEquals(sr, expected);
+    
+    /*
+     * add another codon to the same SearchResults
+     */
+    acf.markMappedRegion(pep1.getDatasetSequence(), 4, sr); // codon positions 15, 17, 18
+    expected.addResult(dna1.getDatasetSequence(),  15, 15);
+    expected.addResult(dna1.getDatasetSequence(),  17, 18);
+    assertEquals(sr, expected);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetCoveringCodonMapping()
+  {
+    SequenceI dna = new Sequence("dna/10-30", "acttcaATGGCGGACtaattt");
+    // CDS sequence with its own dataset sequence (JAL-3763)
+    SequenceI cds = new Sequence("cds/1-9", "-A--TGGC-GGAC");
+    cds.createDatasetSequence();
+    SequenceI pep = new Sequence("pep/1-3", "MAD");
+    
+    /*
+     * with null argument or no mappings
+     */
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    assertNull(acf.getCoveringCodonMapping(null));
+    assertNull(acf.getCoveringCodonMapping(dna));
+    assertNull(acf.getCoveringCodonMapping(pep));
+  
+    /*
+     * with a non-covering mapping e.g. overlapping exon
+     */
+    MapList map = new MapList(new int[] { 16, 18 }, new int[] {
+        1, 1 }, 3, 1);
+    acf.addMap(dna, pep, map);
+    assertNull(acf.getCoveringCodonMapping(dna));
+    assertNull(acf.getCoveringCodonMapping(pep));
+    
+    acf = new AlignedCodonFrame();
+    MapList map2 = new MapList(new int[] { 13, 18 }, new int[] {
+        2, 2 }, 3, 1);
+    acf.addMap(dna, pep, map2);
+    assertNull(acf.getCoveringCodonMapping(dna));
+    assertNull(acf.getCoveringCodonMapping(pep));
+    
+    /*
+     * with a covering mapping from CDS (dataset) to protein
+     */
+    acf = new AlignedCodonFrame();
+    MapList map3 = new MapList(new int[] { 1, 9 }, new int[] {
+        1, 3 }, 3, 1);
+    acf.addMap(cds.getDatasetSequence(), pep, map3);
+    assertNull(acf.getCoveringCodonMapping(dna));
+    SequenceToSequenceMapping mapping = acf.getCoveringCodonMapping(pep);
+    assertNotNull(mapping);
+    SequenceToSequenceMapping mapping2 = acf.getCoveringCodonMapping(cds.getDatasetSequence());
+    assertSame(mapping, mapping2);
+    
+    /*
+     * with a mapping that extends to stop codon
+     * (EMBL CDS location often includes the stop codon)
+     * - getCoveringCodonMapping is lenient (doesn't require exact length match)
+     */
+    SequenceI cds2 = new Sequence("cds/1-12", "-A--TGGC-GGACTAA");
+    cds2.createDatasetSequence();
+    acf = new AlignedCodonFrame();
+    MapList map4 = new MapList(new int[] { 1, 12 }, new int[] {
+        1, 3 }, 3, 1);
+    acf.addMap(cds2, pep, map4);
+    mapping = acf.getCoveringCodonMapping(cds2.getDatasetSequence());
+    assertNotNull(mapping);
+    mapping2 = acf.getCoveringCodonMapping(pep);
+    assertSame(mapping, mapping2);
+  }
 }
index fb0cdae..538dab8 100644 (file)
@@ -46,7 +46,7 @@ public class MapListTest
   {
     Cache.initLogger();
   }
-
+  
   @BeforeClass(alwaysRun = true)
   public void setUpJvOptionPane()
   {
@@ -907,11 +907,12 @@ public class MapListTest
     assertArrayEquals(new int[] { 5, 6 }, merged.get(1));
     assertArrayEquals(new int[] { 12, 8 }, merged.get(2));
     assertArrayEquals(new int[] { 8, 7 }, merged.get(3));
-
+    
     // 'subsumed' ranges are preserved
     ranges.clear();
     ranges.add(new int[] { 10, 30 });
-    ranges.add(new int[] { 15, 25 });
+    ranges.add(new int[] { 15, 25 }); 
+
     merged = MapList.coalesceRanges(ranges);
     assertEquals(2, merged.size());
     assertArrayEquals(new int[] { 10, 30 }, merged.get(0));
index 4b7c75c..feaa948 100644 (file)
@@ -244,7 +244,7 @@ public class MappingUtilsTest
     protein.setCodonFrames(acfList);
 
     /*
-     * Select Seq1 and Seq3 in the protein (startRes=endRes=0)
+     * Select Seq1 and Seq3 in the protein
      */
     SequenceGroup sg = new SequenceGroup();
     sg.setColourText(true);
@@ -252,6 +252,7 @@ public class MappingUtilsTest
     sg.setOutlineColour(Color.LIGHT_GRAY);
     sg.addSequence(protein.getSequenceAt(0), false);
     sg.addSequence(protein.getSequenceAt(2), false);
+    sg.setEndRes(protein.getWidth() - 1);
 
     /*
      * Verify the mapped sequence group in dna
@@ -265,7 +266,7 @@ public class MappingUtilsTest
     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
     assertSame(cdna.getSequenceAt(2), mappedGroup.getSequences().get(1));
     assertEquals(0, mappedGroup.getStartRes());
-    assertEquals(2, mappedGroup.getEndRes());
+    assertEquals(2, mappedGroup.getEndRes()); // 3 columns (1 codon)
 
     /*
      * Verify mapping sequence group from dna to protein
@@ -1346,4 +1347,105 @@ public class MappingUtilsTest
     overlap = MappingUtils.findOverlap(ranges, 13, 15);
     assertNull(overlap);
   }
+  
+  /**
+   * Test mapping a sequence group where sequences in and outside the group
+   * share a dataset sequence (e.g. alternative CDS for the same gene)
+   * <p>
+   * This scenario doesn't arise after JAL-3763 changes, but test left as still valid
+   * @throws IOException
+   */
+  @Test(groups = { "Functional" })
+  public void testMapSequenceGroup_sharedDataset() throws IOException
+  {
+    /*
+     * Set up dna and protein Seq1/2/3 with mappings (held on the protein
+     * viewport). CDS sequences share the same 'gene' dataset sequence.
+     */
+    SequenceI dna = new Sequence("dna", "aaatttgggcccaaatttgggccc");
+    SequenceI cds1 = new Sequence("cds1/1-6", "aaattt");
+    SequenceI cds2 = new Sequence("cds1/4-9", "tttggg");
+    SequenceI cds3 = new Sequence("cds1/19-24", "gggccc");
+
+    cds1.setDatasetSequence(dna);
+    cds2.setDatasetSequence(dna);
+    cds3.setDatasetSequence(dna);
+
+    SequenceI pep1 = new Sequence("pep1", "KF");
+    SequenceI pep2 = new Sequence("pep2", "FG");
+    SequenceI pep3 = new Sequence("pep3", "GP");
+    pep1.createDatasetSequence();
+    pep2.createDatasetSequence();
+    pep3.createDatasetSequence();
+
+    /*
+     * add mappings from coding positions of dna to respective peptides
+     */
+    AlignedCodonFrame acf = new AlignedCodonFrame();
+    acf.addMap(dna, pep1,
+            new MapList(new int[]
+            { 1, 6 }, new int[] { 1, 2 }, 3, 1));
+    acf.addMap(dna, pep2,
+            new MapList(new int[]
+            { 4, 9 }, new int[] { 1, 2 }, 3, 1));
+    acf.addMap(dna, pep3,
+            new MapList(new int[]
+            { 19, 24 }, new int[] { 1, 2 }, 3, 1));
+
+    List<AlignedCodonFrame> acfList = Arrays
+            .asList(new AlignedCodonFrame[]
+            { acf });
+
+    AlignmentI cdna = new Alignment(new SequenceI[] { cds1, cds2, cds3 });
+    AlignmentI protein = new Alignment(
+            new SequenceI[]
+            { pep1, pep2, pep3 });
+    AlignViewportI cdnaView = new AlignViewport(cdna);
+    AlignViewportI peptideView = new AlignViewport(protein);
+    protein.setCodonFrames(acfList);
+
+    /*
+     * Select pep1 and pep3 in the protein alignment
+     */
+    SequenceGroup sg = new SequenceGroup();
+    sg.setColourText(true);
+    sg.setIdColour(Color.GREEN);
+    sg.setOutlineColour(Color.LIGHT_GRAY);
+    sg.addSequence(pep1, false);
+    sg.addSequence(pep3, false);
+    sg.setEndRes(protein.getWidth() - 1);
+
+    /*
+     * Verify the mapped sequence group in dna is cds1 and cds3
+     */
+    SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
+            peptideView, cdnaView);
+    assertTrue(mappedGroup.getColourText());
+    assertSame(sg.getIdColour(), mappedGroup.getIdColour());
+    assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
+    assertEquals(2, mappedGroup.getSequences().size());
+    assertSame(cds1, mappedGroup.getSequences().get(0));
+    assertSame(cds3, mappedGroup.getSequences().get(1));
+    // columns 1-6 selected (0-5 base zero)
+    assertEquals(0, mappedGroup.getStartRes());
+    assertEquals(5, mappedGroup.getEndRes());
+
+    /*
+     * Select mapping sequence group from dna to protein
+     */
+    sg.clear();
+    sg.addSequence(cds2, false);
+    sg.addSequence(cds1, false);
+    sg.setStartRes(0);
+    sg.setEndRes(cdna.getWidth() - 1);
+    mappedGroup = MappingUtils.mapSequenceGroup(sg, cdnaView, peptideView);
+    assertTrue(mappedGroup.getColourText());
+    assertSame(sg.getIdColour(), mappedGroup.getIdColour());
+    assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
+    assertEquals(2, mappedGroup.getSequences().size());
+    assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(0));
+    assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(1));
+    assertEquals(0, mappedGroup.getStartRes());
+    assertEquals(1, mappedGroup.getEndRes()); // two columns
+  }
 }
index 3e7e153..15b162f 100644 (file)
@@ -16,4 +16,4 @@ uod_banner.28=/images/UoD_banner-28.png
 uod_banner.30=/images/UoD_banner-30.png
 uod_banner.32=/images/UoD_banner-32.png
 default_appbase=https://www.jalview.org/getdown/release/1.8
-preferences.filename=.jalview_properties
+preferences.filename=.jalview_nonrelease_properties
index f281469..31a4afa 100755 (executable)
@@ -39,7 +39,7 @@ my $add_associations = {
   annotations => {shortname=>"annotations",name=>"Jalview Annotations",extensions=>["annotations","jvannotations"]},
   mmcif => {shortname=>"mmcif",name=>"CIF",extensions=>["cif"]},
   mmcif2 => {shortname=>"mmcif2",name=>"mmCIF",extensions=>["mcif","mmcif"]},
-  jvl => {shortname=>"jvl",name=>"Jalview Launch",extensions=>["jvl"],iconfile=>"Jalview-Launch"},
+  jvl => {shortname=>"jvl",name=>"Jalview Launch",extensions=>["jvl"],iconfile=>"jvl_file"},
   jnet => {shortname=>"jnet",name=>"JnetFile",extensions=>["concise","jnet"]},
   scorematrix => {shortname=>"scorematrix",name=>"Substitution Matrix",extensions=>["mat"]},
 };
index 2196a6a..0b927a8 100644 (file)
@@ -28,7 +28,7 @@
 <key>CFBundleTypeName</key>
 <string>Jalview Launch File</string>
 <key>CFBundleTypeIconFile</key>
-<string>Jalview-Launch.icns</string>
+<string>jvl_file.icns</string>
 <key>CFBundleTypeRole</key>
 <string>Editor</string>
 <key>CFBundleTypeMIMETypes</key>
index a4bdc49..b995078 100644 (file)
@@ -66,7 +66,7 @@
                       <property name="launcherId" type="string">JALVIEW</property>
                       <property name="macIconFile">
                         <object class="com.install4j.api.beans.ExternalFile">
-                          <string>Jalview-Launch.icns</string>
+                          <string>jvl_file.icns</string>
                         </object>
                       </property>
                       <property name="macRole" type="enum" class="com.install4j.runtime.beans.actions.desktop.MacAssociationRole" value="EDITOR" />
                       <property name="unix" type="boolean" value="true" />
                       <property name="unixIconFile">
                         <object class="com.install4j.api.beans.ExternalFile">
-                          <string>Jalview-Launch.png</string>
+                          <string>jvl_file.png</string>
                         </object>
                       </property>
                       <property name="unixMimeType" type="string">application/x-jalview-jvl+text</property>
                       <property name="windowsIconFile">
                         <object class="com.install4j.api.beans.ExternalFile">
-                          <string>Jalview-Launch.ico</string>
+                          <string>jvl_file.ico</string>
                         </object>
                       </property>
                     </serializedBean>
index b0828eb..e4e2193 100644 (file)
@@ -1287,7 +1287,7 @@ return console.askYesNo(message, true);
         <file name=".background/jalview_dmg_background.png" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_BG_IMAGE}" />
         <file name=".DS_Store" file="${compiler:JALVIEW_DIR}/${compiler:MACOS_DMG_DS_STORE}" />
         <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/Jalview-File.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/Jalview-File.icns" />
-        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/Jalview-Launch.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/Jalview-Launch.icns" />
+        <file name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/Resources/jvl_file.icns" file="${compiler:JALVIEW_DIR}/${compiler:INSTALL4J_UTILS_DIR}/jvl_file.icns" />
         <symlink name="${compiler:JALVIEW_APPLICATION_NAME}.app/Contents/MacOS/${compiler:WRAPPER_LINK}" target="../Resources/app/${compiler:WRAPPER_SCRIPT_BIN_DIR}/${compiler:BASH_WRAPPER_SCRIPT}" />
       </topLevelFiles>
     </macosArchive>
diff --git a/utils/install4j/jvl_file.icns b/utils/install4j/jvl_file.icns
new file mode 100644 (file)
index 0000000..8230feb
Binary files /dev/null and b/utils/install4j/jvl_file.icns differ
diff --git a/utils/install4j/jvl_file.ico b/utils/install4j/jvl_file.ico
new file mode 100644 (file)
index 0000000..8811bc2
Binary files /dev/null and b/utils/install4j/jvl_file.ico differ
diff --git a/utils/install4j/jvl_file.png b/utils/install4j/jvl_file.png
new file mode 100644 (file)
index 0000000..56e9eae
Binary files /dev/null and b/utils/install4j/jvl_file.png differ
diff --git a/utils/install4j/jvl_file.svg b/utils/install4j/jvl_file.svg
new file mode 100644 (file)
index 0000000..c6e01be
--- /dev/null
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 85.33334 85.333347"
+   enable-background="new 0 0 595.238 595.238"
+   xml:space="preserve"
+   sodipodi:docname="jvl_file.svg"
+   width="85.333344"
+   height="85.333344"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="2152"
+   inkscape:window-height="1410"
+   id="namedview11"
+   showgrid="false"
+   fit-margin-top="0"
+   fit-margin-left="0"
+   fit-margin-right="0"
+   fit-margin-bottom="0"
+   inkscape:zoom="2.7754382"
+   inkscape:cx="42.666672"
+   inkscape:cy="42.515809"
+   inkscape:window-x="0"
+   inkscape:window-y="0"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1" />
+  <metadata
+   id="metadata41">
+    <rdf:RDF>
+      <cc:Work
+   rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+   rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+   id="defs39">
+    
+    
+    
+    
+    
+    
+  </defs>
+  <g
+   id="g4756"
+   transform="translate(4.0769397e-6,-0.15086052)"><rect
+     style="fill:#0084a9;stroke-width:1"
+     id="rect2"
+     height="20.911402"
+     width="20.91173"
+     y="32.353989"
+     x="32.057495" /><path
+     inkscape:connector-curvature="0"
+     id="polygon4"
+     d="M 0,0.30172379 0.00572055,53.26261 H 10.525655 V 10.821168 H 68.897858 L 58.376942,0.30172379 Z"
+     style="fill:#ad208e;stroke-width:1" /><path
+     inkscape:connector-curvature="0"
+     id="polygon6"
+     d="m 26.699622,26.995298 h 31.67732 L 68.897858,16.921076 H 16.62115 v 36.329112 h 10.078472 z"
+     style="fill:#f78e1e;stroke-width:1" /><polygon
+     transform="matrix(0.16344439,0,0,0.16344439,-6.339681,-5.9678396)"
+     style="fill:#009ddc;stroke-width:6.11828899"
+     id="polygon8"
+     points="137.861,494.236 202.225,558.595 560.882,558.607 560.848,234.574 496.501,234.574 496.501,494.26 " /><polygon
+     transform="matrix(0.16344439,0,0,0.16344439,-6.339681,-5.9678396)"
+     style="fill:#c1d82f;stroke-width:6.11828899"
+     id="polygon10"
+     points="457.329,234.646 395.688,234.646 395.688,395.282 38.904,395.282 100.535,456.929 457.329,456.929 " /><path
+     inkscape:connector-curvature="0"
+     style="fill:none;stroke:#000000;stroke-width:0.1"
+     id="path32"
+     d="M 0.43525241,58.050222" /></g>
+</svg>
\ No newline at end of file