JAL-2835 spike updated with latest
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 15 Nov 2017 10:51:51 +0000 (10:51 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 15 Nov 2017 10:51:51 +0000 (10:51 +0000)
55 files changed:
.classpath
benchmarking/README
benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java
build.xml
help/helpTOC.xml
help/html/features/pdbseqfetcher.png
help/html/features/pdbsequencefetcher.html
help/html/features/uniprotseqfetcher.png
help/html/features/uniprotsequencefetcher.html
help/html/releases.html
help/html/whatsNew.html
lib/groovy-all-2.4.12-indy.jar [new file with mode: 0644]
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/api/FeatureColourI.java
src/jalview/api/FeatureRenderer.java
src/jalview/appletgui/FeatureColourChooser.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/features/FeatureAttributes.java
src/jalview/ext/ensembl/EnsemblGenomes.java
src/jalview/fts/api/GFTSPanelI.java
src/jalview/fts/core/GFTSPanel.java
src/jalview/fts/service/pdb/PDBFTSPanel.java
src/jalview/fts/service/uniprot/UniprotFTSPanel.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/FeatureColourChooser.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/OverviewCanvas.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/SequenceFetcher.java
src/jalview/gui/StructureChooser.java
src/jalview/gui/StructureViewer.java
src/jalview/io/AlignFile.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/cache/JvCacheableInputBox.java
src/jalview/io/vcf/VCFLoader.java
src/jalview/schemes/FeatureColour.java
src/jalview/util/matcher/KeyedMatcher.java
src/jalview/util/matcher/KeyedMatcherI.java
src/jalview/util/matcher/KeyedMatcherSet.java
src/jalview/util/matcher/KeyedMatcherSetI.java
src/jalview/util/matcher/Matcher.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
test/jalview/datamodel/features/FeatureAttributesTest.java [new file with mode: 0644]
test/jalview/gui/AlignViewportTest.java
test/jalview/gui/StructureViewerTest.java
test/jalview/schemes/Blosum62ColourSchemeTest.java
test/jalview/util/MapListTest.java
test/jalview/util/MathUtilsTest.java
test/jalview/util/matcher/ConditionTest.java
test/jalview/util/matcher/KeyedMatcherSetTest.java
test/jalview/util/matcher/KeyedMatcherTest.java
test/jalview/util/matcher/MatcherTest.java

index 441ba60..c85feaf 100644 (file)
@@ -66,8 +66,8 @@
        <classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
        <classpathentry kind="lib" path="lib/biojava-core-4.1.0.jar"/>
        <classpathentry kind="lib" path="lib/biojava-ontology-4.1.0.jar"/>
-       <classpathentry kind="lib" path="lib/groovy-all-2.4.6-indy.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
        <classpathentry kind="lib" path="lib/htsjdk-2.12.0.jar"/>
+       <classpathentry kind="lib" path="lib/groovy-all-2.4.12-indy.jar"/>
        <classpathentry kind="output" path="classes"/>
 </classpath>
index 60b94a9..fac0bf6 100644 (file)
@@ -1,11 +1,13 @@
 To set up benchmarking:
 
-1. In the jalview directory run 
+You will need to install Maven: https://maven.apache.org/install.html
+
+1. Run the makedist target of build.xml in Eclipse, or in the jalview directory run 
   ant makedist
 
 This builds a jalview.jar file and puts it into dist/
 
-2. Make a lib directory in benchmarking/ if not already present.
+2. Make a lib directory in benchmarking/ if not already present and cd into this directory.
 
 3. Purge any previous maven dependencies:
    mvn dependency:purge-local-repository -DactTransitively=false -DreResolve=false
index eb35e3b..d3c67d7 100644 (file)
@@ -47,126 +47,128 @@ import jalview.datamodel.HiddenColumns;
 @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
 @Fork(1)
 public class HiddenColumnsBenchmark 
-{      
-       /*
-        * State with multiple hidden columns and a start position set
-        */
-       @State(Scope.Thread)
-       public static class HiddenColsAndStartState
-       {
-               @Param({"300", "10000", "100000"})
-               public int maxcols;
-               
-               @Param({"1", "50", "90"})
-               public int startpcnt; // position as percentage of maxcols
-               
-               @Param({"1","15","100"})
-               public int hide;
-               
-               HiddenColumns h = new HiddenColumns();
-               Random rand = new Random();
-               
-               public int hiddenColumn;
-               public int visibleColumn;
-       
-               @Setup
-               public void setup()
-               {
-                       rand.setSeed(1234);
-                       int lastcol = 0;
-               while (lastcol < maxcols)
-               {
-                       int count = rand.nextInt(100); 
-                       lastcol += count;
-                       h.hideColumns(lastcol, lastcol+hide);
-                       lastcol+=hide;
-               }
-               
-               // make sure column at start is hidden
-               hiddenColumn = (int)(maxcols * startpcnt/100.0);
-               h.hideColumns(hiddenColumn, hiddenColumn);
-               
-               // and column <hide> after start is visible
-               ColumnSelection sel = new ColumnSelection();
-               h.revealHiddenColumns(hiddenColumn+hide, sel);
-               visibleColumn = hiddenColumn+hide;
-               
-               System.out.println("Maxcols: " + maxcols + " HiddenCol: " + hiddenColumn + " Hide: " + hide);
-               System.out.println("Number of hidden columns: " + h.getSize());
-               }
-       }
-       
-       /* Convention: functions in alphabetical order */
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-       public int benchAdjustForHiddenColumns(HiddenColsAndStartState tstate)
-       {
-               return tstate.h.adjustForHiddenColumns(tstate.visibleColumn);
-       }
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-       public int benchFindColumnPosition(HiddenColsAndStartState tstate)
-       {
-               return tstate.h.findColumnPosition(tstate.visibleColumn);
-       }
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-       public List<Integer> benchFindHiddenRegionPositions(HiddenColsAndStartState tstate)
-       {
-               return tstate.h.findHiddenRegionPositions();
-       }
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-       public ArrayList<int[]> benchGetHiddenColumnsCopy(HiddenColsAndStartState tstate)
-       {
-               return tstate.h.getHiddenColumnsCopy();
-       }
-       
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-    public int benchGetSize(HiddenColsAndStartState tstate)
-    {
-               return tstate.h.getSize();
-    }
+{
+  /*
+   * State with multiple hidden columns and a start position set
+   */
+  @State(Scope.Thread)
+  public static class HiddenColsAndStartState
+  {
+    @Param({ "300", "10000", "100000" })
+    public int maxcols;
 
-   @Benchmark
-    @BenchmarkMode({Mode.Throughput})
-    public HiddenColumns benchHideCols(HiddenColsAndStartState tstate) 
-    {
-       tstate.h.hideColumns(tstate.visibleColumn,
-                       tstate.visibleColumn+2000); 
-       return tstate.h;
-    }
-   
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-    public boolean benchIsVisible(HiddenColsAndStartState tstate) 
-    {
-       return tstate.h.isVisible(tstate.hiddenColumn); 
-    }
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-    public HiddenColumns benchReveal(HiddenColsAndStartState tstate) 
-    {
-               ColumnSelection sel = new ColumnSelection();
-       tstate.h.revealHiddenColumns(tstate.hiddenColumn, sel);
-       return tstate.h;
-    }
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-    public HiddenColumns benchRevealAll(HiddenColsAndStartState tstate) 
+    @Param({ "1", "50", "90" })
+    public int startpcnt; // position as percentage of maxcols
+
+    @Param({ "1", "15", "100" })
+    public int hide;
+
+    HiddenColumns h = new HiddenColumns();
+
+    Random rand = new Random();
+
+    public int hiddenColumn;
+
+    public int visibleColumn;
+
+    @Setup
+    public void setup()
     {
-               ColumnSelection sel = new ColumnSelection();
-       tstate.h.revealAllHiddenColumns(sel);
-       return tstate.h;
+      rand.setSeed(1234);
+      int lastcol = 0;
+      while (lastcol < maxcols)
+      {
+        int count = rand.nextInt(100);
+        lastcol += count;
+        h.hideColumns(lastcol, lastcol + hide);
+        lastcol += hide;
+      }
+
+      // make sure column at start is hidden
+      hiddenColumn = (int) (maxcols * startpcnt / 100.0);
+      h.hideColumns(hiddenColumn, hiddenColumn);
+
+      // and column <hide> after start is visible
+      ColumnSelection sel = new ColumnSelection();
+      h.revealHiddenColumns(hiddenColumn + hide, sel);
+      visibleColumn = hiddenColumn + hide;
+
+      System.out.println("Maxcols: " + maxcols + " HiddenCol: "
+              + hiddenColumn + " Hide: " + hide);
+      System.out.println("Number of hidden columns: " + h.getSize());
     }
-       
-       
+  }
+
+  /* Convention: functions in alphabetical order */
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public int benchAdjustForHiddenColumns(HiddenColsAndStartState tstate)
+  {
+    return tstate.h.adjustForHiddenColumns(tstate.visibleColumn);
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public int benchFindColumnPosition(HiddenColsAndStartState tstate)
+  {
+    return tstate.h.findColumnPosition(tstate.visibleColumn);
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public List<Integer> benchFindHiddenRegionPositions(
+          HiddenColsAndStartState tstate)
+  {
+    return tstate.h.findHiddenRegionPositions();
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public ArrayList<int[]> benchGetHiddenColumnsCopy(
+          HiddenColsAndStartState tstate)
+  {
+    return tstate.h.getHiddenColumnsCopy();
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public int benchGetSize(HiddenColsAndStartState tstate)
+  {
+    return tstate.h.getSize();
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public HiddenColumns benchHideCols(HiddenColsAndStartState tstate)
+  {
+    tstate.h.hideColumns(tstate.visibleColumn, tstate.visibleColumn + 2000);
+    return tstate.h;
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public boolean benchIsVisible(HiddenColsAndStartState tstate)
+  {
+    return tstate.h.isVisible(tstate.hiddenColumn);
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public HiddenColumns benchReveal(HiddenColsAndStartState tstate)
+  {
+    ColumnSelection sel = new ColumnSelection();
+    tstate.h.revealHiddenColumns(tstate.hiddenColumn, sel);
+    return tstate.h;
+  }
+
+  @Benchmark
+  @BenchmarkMode({ Mode.Throughput })
+  public HiddenColumns benchRevealAll(HiddenColsAndStartState tstate)
+  {
+    ColumnSelection sel = new ColumnSelection();
+    tstate.h.revealAllHiddenColumns(sel);
+    return tstate.h;
+  }
+
 }
\ No newline at end of file
index 436148a..89c3d24 100755 (executable)
--- a/build.xml
+++ b/build.xml
 
     <jnlpf toFile="${jnlpFile}" />
     <!-- add the add-modules j2se attribute for java 9 -->
-    <replace file="${jnlpFile}" value="j2se version=&quot;1.8+&quot; initial-heap-size=&quot;${inih}&quot; max-heap-size=&quot;${maxh}&quot; java-vm-args=&quot;--add-modules=java.se.ee&quot;">
+    <replace file="${jnlpFile}" value="j2se version=&quot;1.8+&quot; initial-heap-size=&quot;${inih}&quot; max-heap-size=&quot;${maxh}&quot; java-vm-args=&quot;--add-modules=java.se.ee --illegal-access=warn&quot;">
           <replacetoken>j2se version="1.8+"</replacetoken>
-           
-        </replace>
+    </replace>
   </target>
 
   <target name="-dofakejnlpfileassoc" depends="-generatejnlpf" if="nojnlpfileassocs">
index 4636ea3..7ba4ee5 100755 (executable)
        <tocitem text="Jalview Documentation" target="home" expand="true">
                        <tocitem text="What's new" target="new" expand="true">
                                <tocitem text="Latest Release Notes" target="release"/>
-        <tocitem text="Calculations Dialog" target="calcs.dialog"/>
-                               <tocitem text="Groovy Features Counter example" target="groovy.featurescounter"/>
-                               <tocitem text="Custom Colourschemes in Groovy" target="groovy.colours"/>
-                               <tocitem text="Omit hidden regions in Overview" target="overview"/>
-                               <tocitem text="Show gaps as grey in overview" target="overviewprefs"/>
-                               <tocitem text="identifers.org for URL Links" target="linksprefs" />
-                               <tocitem text="New features in Split Frame View" target="splitframe.mirrorfonts" />
+        
                </tocitem>
                
                <tocitem text="Editing Alignments" target="edit" />
index 97a779a..2081a3d 100644 (file)
Binary files a/help/html/features/pdbseqfetcher.png and b/help/html/features/pdbseqfetcher.png differ
index 2962ba6..bb63bed 100644 (file)
@@ -37,7 +37,7 @@
   <p>
     To open the PDB Sequence Fetcher, select PDB as the database from
     any <a href="seqfetch.html">Sequence Fetcher</a> dialog (opened <em>via</em>
-    <strong>&quot;File &#8594;Fetch Sequences&quot;</strong>).
+    <strong>&quot;File &#8594;Fetch Sequences&quot;</strong>). 
   </p>
   <img src="pdbseqfetcher.png" align="left"
     alt="PDB sequence fetcher (introduced in Jalview 2.9)" />
   <p>
     <strong>Searching the PDB Database</strong>
   </p>
+  <p>To search the PDB, begin typing in the text box. If the
+    'autosearch' checkbox is enabled, then the results of your query
+    will be automatically updated and shown in the search results tab;
+    otherwise, press return to update the results. To access previous
+    searches, press the down-arrow or click the drop down menu icon at
+    the side of the search box. If you just want to paste in a list of
+    IDs, the 'Retrieve IDs' tab provides a batch-retrieval interface.</p>
   <p>
-    To search the PDB, begin typing in the text box. The results of your
-    query are shown in the search results tab, which updates every time
-    you type in the search text box. You can sort results according to
-    the displayed columns, and select entries with the mouse and
-    keyboard. Once you have selected one or more entries, hit the <strong>OK</strong>
-    button to retrieve and view them in Jalview.
+    You can sort results according to the displayed columns, and select
+    entries with the mouse and keyboard. Once you have selected one or
+    more entries, hit the <strong>OK</strong> button to retrieve and
+    view them in Jalview.
   </p>
   <p>
   <ul>
@@ -64,9 +69,8 @@
       1xyz:A</li>
 
     <li><strong>Bulk PDB retrieval</strong><br>Multiple PDB
-      IDs can be specified by separating them with a semi-colon.<br />
-      e.g. 1xyz;2xyz;3xyz<br />Hitting Return or OK will automatically
-      fetch those IDs, like the default Sequence Fetcher interface.</li>
+      IDs can be specified for retrieval via the 
+      <strong>Retrieve IDs</strong> tab.</li>
 
     <li><strong>Wild card searching</strong><br>The following
       wild cards are supported by the EMBL-EBI PDBe query service:
index a592e8e..23b55fa 100644 (file)
Binary files a/help/html/features/uniprotseqfetcher.png and b/help/html/features/uniprotseqfetcher.png differ
index edd8995..4a64f52 100644 (file)
   <p>
     <strong>Searching the UniProt Database</strong>
   </p>
-  <p>
-    To search UniProt, simply begin typing in the text box. After a
-    short delay (about 1.5 seconds), results will be shown in the table
-    below. You can sort results by clicking on the displayed columns,
+  <p>To search UniProt, simply begin typing in the text box. If the
+    'autosearch' check box is enabled, then after a short delay (about
+    1.5 seconds), results will be shown in the table below. Results are
+    also updated whenever you press Enter, and you can access previous
+    searches by pressing the 'Down' arrow or clicking the drop-down menu
+    icon at the side of the search box.</p>
+  <p>You can sort results by clicking on the displayed columns,
     and select entries with the mouse or keyboard. Once you have
     selected one or more entries, hit the <strong>OK</strong> button to
     retrieve the sequences.
 
 
     <li><strong>Bulk UniProt record retrieval</strong><br> To
-      retrieve several uniprot accessions at once, first select <strong>UniProt
-        ID</strong> from the dropdown menu, then paste in the accession IDs as a
-      semi-colon separated list. (e.g. fila_human; mnt_human;
-      mnt_mouse).<br />Hitting Return or OK will automatically fetch
-      those IDs, like the default Sequence Fetcher interface.</li>
+      retrieve sequences for a list of Uniprot accessions, please enter
+      them via the 'Retrieve IDs' tab.</li>
 
     <li><strong><a name="text-search">Complex queries
           with the UniProt query Syntax</a></strong> The text box also allows complex
index 7be088e..6396313 100755 (executable)
@@ -71,7 +71,7 @@ li:before {
       <td width="60" nowrap>
         <div align="center">
           <strong><a name="Jalview.2.10.3">2.10.3</a><br />
-            <em>10/10/2017</em></strong>
+            <em>14/11/2017</em></strong>
         </div>
       </td>
       <td><div align="left">
@@ -95,8 +95,15 @@ li:before {
             
             <li><!-- JAL-2617 -->Stop codons are excluded in CDS/Protein view from Ensembl locus cross-references</li>
             <li><!-- JAL-2685 -->Start/End limits are shown in Pairwise Alignment report</li>
+            <li><!-- JAL-2810 -->Sequence fetcher's Free text 'autosearch' feature can be disabled</li>
+            <li><!-- JAL-2810 -->Retrieve IDs tab added for UniProt and PDB easier retrieval of sequences for lists of IDs</li>
+             
+          </ul>
+          <em>Scripting</em>
+          <ul>
+          <li>Groovy interpreter updated to 2.4.12</li>
+          <li>Example groovy script for generating a matrix of percent identity scores for current alignment.</li>
           </ul>
-          <ul><li>Example groovy script for generating a matrix of percent identity scores for current alignment.</li></ul>
           <em>Testing and Deployment</em>
           <ul><li><!-- JAL-2727 -->Test to catch memory leaks in Jalview UI</li></ul>
           </div>
@@ -133,6 +140,8 @@ li:before {
             <li><!-- JAL-2973 -->Alignment ruler height set incorrectly after canceling the Alignment Window's Font dialog</li>
             <li><!-- JAL-2036 -->Show cross-references not enabled after restoring project until a new view is created</li>
             <li><!-- JAL-2756 -->Warning popup about use of SEQUENCE_ID in URL links appears when only default EMBL-EBI link is configured (since 2.10.2b2)</li>            
+            <li><!-- JAL-2775 -->Overview redraws whole window when box position is adjusted</li>
+            <li><!-- JAL-2225 -->Structure viewer doesn't map all chains in a multi-chain structure when viewing alignment involving more than one chain (since 2.10)</li>            
            </ul>
           <strong><em>Applet</em></strong><br/>
            <ul>
@@ -144,6 +153,17 @@ li:before {
             <!-- JAL-2546 -->BioJSON export does not preserve non-positional features
           </li>
           </ul>
+          <strong>Known Java 9 Issues</strong>
+          <ul>
+            <li><!-- JAL-2902 -->Groovy Console very slow to open and is 
+            not responsive when entering characters (Webstart, Java 9.01, 
+            OSX 10.10)
+            </li>
+          </ul>
+          <strong>New Known Issues</strong>
+          <ul>
+          <li><!-- JAL- --></li>
+          </ul>
           </div>
       </td>
     </tr>
index 3475012..9cf1044 100755 (executable)
     <strong>What's new in Jalview 2.10.3 ?</strong>
   </p>
   <p>
-    Version 2.10.3 is due for release in October 2017. The full list of
+    Version 2.10.3 was released in November 2017. The full list of
     bug fixes and new features can be found in the <a
       href="releases.html#Jalview.2.10.3"> 2.10.3 Release Notes</a>, but
     the highlights are below.
   </p>
+  <ul>
+    <li>Faster import and more responsive UI when working with wide alignments and handling hundreds and thousands of sequence features</li>
+    <li> 
+    <li>Improved usability with <a href="features/pdbsequencefetcher.html">PDB</a> and 
+    <a href="features/uniprotsequencefetcher.html">UniProt</a> Free Text Search
+      dialog, and new tab for retrieval of sequences for lists of IDs.</li>
+  </ul>
   <p>
     <strong><a name="experimental">Experimental Features</a></strong>
   </p>
diff --git a/lib/groovy-all-2.4.12-indy.jar b/lib/groovy-all-2.4.12-indy.jar
new file mode 100644 (file)
index 0000000..bb246a3
Binary files /dev/null and b/lib/groovy-all-2.4.12-indy.jar differ
index 4626c39..9f1c71b 100644 (file)
@@ -784,7 +784,7 @@ label.pairwise_aligned_sequences = Pairwise Aligned Sequences
 label.original_data_for_params = Original Data for {0}
 label.points_for_params = Points for {0}
 label.transformed_points_for_params = Transformed points for {0}
-label.graduated_color_for_params = Graduated Feature Colour for {0}
+label.variable_color_for = Variable Feature Colour for {0}
 label.select_background_colour = Select Background Colour
 label.invalid_font = Invalid Font
 label.separate_multiple_accession_ids = Enter one or more accession IDs separated by a semi-colon ";"
@@ -1349,3 +1349,7 @@ label.attribute = Attribute
 label.colour_by_label = Colour by label
 label.variable_colour = Variable colour
 label.select_new_colour = Select new colour
+label.no_feature_attributes = No feature attributes found
+option.enable_disable_autosearch = When ticked, search is performed automatically.
+option.autosearch = Autosearch
+label.retrieve_ids = Retrieve IDs
index e8fd411..a7fff8e 100644 (file)
@@ -708,7 +708,7 @@ label.pairwise_aligned_sequences = Secuencias alineadas a pares
 label.original_data_for_params = Datos originales de {0}
 label.points_for_params = Puntos de {0}
 label.transformed_points_for_params = Puntos transformados de {0}
-label.graduated_color_for_params = Color graduado para la característica de {0}
+label.variable_color_for = Color variable para la característica de {0}
 label.select_background_colour = Seleccionar color de fondo
 label.invalid_font = Fuente no válida
 label.separate_multiple_accession_ids = Separar los accession id con un punto y coma ";"
index 3eebf6c..93773cc 100644 (file)
@@ -189,17 +189,18 @@ public interface FeatureColourI
   boolean isColourByAttribute();
 
   /**
-   * Answers the name of the attribute used for colouring if any, or null
+   * Answers the name of the attribute (and optional sub-attribute...) used for
+   * colouring if any, or null
    * 
    * @return
    */
-  String getAttributeName();
+  String[] getAttributeName();
 
   /**
-   * Sets the name of the attribute used for colouring if any, or null to remove
-   * this property
+   * Sets the name of the attribute (and optional sub-attribute...) used for
+   * colouring if any, or null to remove this property
    * 
    * @return
    */
-  void setAttributeName(String name);
+  void setAttributeName(String... name);
 }
index 40c7d4d..ef0abbd 100644 (file)
@@ -133,7 +133,7 @@ public interface FeatureRenderer
   List<String> getGroups(boolean visible);
 
   /**
-   * change visibility for a range of groups
+   * Set visibility for a list of groups
    * 
    * @param toset
    * @param visible
@@ -141,7 +141,7 @@ public interface FeatureRenderer
   void setGroupVisibility(List<String> toset, boolean visible);
 
   /**
-   * change visibiilty of given group
+   * Set visibility of the given feature group
    * 
    * @param group
    * @param visible
@@ -149,9 +149,9 @@ public interface FeatureRenderer
   void setGroupVisibility(String group, boolean visible);
 
   /**
-   * Returns features at the specified aligned column on the given sequence.
-   * Non-positional features are not included. If the column has a gap, then
-   * enclosing features are included (but not contact features).
+   * Returns visible features at the specified aligned column on the given
+   * sequence. Non-positional features are not included. If the column has a gap,
+   * then enclosing features are included (but not contact features).
    * 
    * @param sequence
    * @param column
index 0d85c60..e9c377a 100644 (file)
@@ -167,9 +167,9 @@ public class FeatureColourChooser extends Panel implements ActionListener,
     slider.addAdjustmentListener(this);
     slider.addMouseListener(this);
     owner = (af != null) ? af : fs.frame;
-    frame = new JVDialog(owner, MessageManager
-            .formatMessage("label.graduated_color_for_params", new String[]
-            { type }), true, 480, 248);
+    frame = new JVDialog(owner, MessageManager.formatMessage(
+            "label.variable_color_for", new String[] { type }), true, 480,
+            248);
     frame.setMainPanel(this);
     validate();
     frame.setVisible(true);
index 1905f42..9e81d81 100755 (executable)
@@ -77,11 +77,6 @@ public class Sequence extends ASequence implements SequenceI
    */
   Vector<AlignmentAnnotation> annotation;
 
-  /**
-   * The index of the sequence in a MSA
-   */
-  int index = -1;
-
   private SequenceFeaturesI sequenceFeatureStore;
 
   /*
@@ -1739,30 +1734,6 @@ public class Sequence extends ASequence implements SequenceI
     }
   }
 
-  /**
-   * @return The index (zero-based) on this sequence in the MSA. It returns
-   *         {@code -1} if this information is not available.
-   */
-  @Override
-  public int getIndex()
-  {
-    return index;
-  }
-
-  /**
-   * Defines the position of this sequence in the MSA. Use the value {@code -1}
-   * if this information is undefined.
-   * 
-   * @param The
-   *          position for this sequence. This value is zero-based (zero for
-   *          this first sequence)
-   */
-  @Override
-  public void setIndex(int value)
-  {
-    index = value;
-  }
-
   @Override
   public void setRNA(RNA r)
   {
index 2110632..8a6cb61 100755 (executable)
@@ -59,18 +59,6 @@ public class SequenceFeature implements FeatureLocationI
   private static final String ROW_DATA = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>";
 
   /*
-   * map of otherDetails special keys, and their value fields' delimiter
-   */
-  private static final Map<String, String> INFO_KEYS = new HashMap<>();
-
-  static
-  {
-    INFO_KEYS.put("CSQ", ",");
-    // todo capture second level metadata (CSQ FORMAT)
-    // and delimiter "|" so as to report in a table within a table?
-  }
-
-  /*
    * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as
    * name1=value1;name2=value2,value3;...etc
    */
@@ -184,7 +172,7 @@ public class SequenceFeature implements FeatureLocationI
 
     if (sf.otherDetails != null)
     {
-      otherDetails = new HashMap<String, Object>();
+      otherDetails = new HashMap<>();
       for (Entry<String, Object> entry : sf.otherDetails.entrySet())
       {
         otherDetails.put(entry.getKey(), entry.getValue());
@@ -192,7 +180,7 @@ public class SequenceFeature implements FeatureLocationI
     }
     if (sf.links != null && sf.links.size() > 0)
     {
-      links = new Vector<String>();
+      links = new Vector<>();
       for (int i = 0, iSize = sf.links.size(); i < iSize; i++)
       {
         links.addElement(sf.links.elementAt(i));
@@ -359,7 +347,7 @@ public class SequenceFeature implements FeatureLocationI
   {
     if (links == null)
     {
-      links = new Vector<String>();
+      links = new Vector<>();
     }
 
     if (!links.contains(labelLink))
@@ -394,18 +382,25 @@ public class SequenceFeature implements FeatureLocationI
 
   /**
    * Answers the value of the specified attribute as string, or null if no such
-   * value
+   * value. If more than one attribute name is provided, tries to resolve as keys
+   * to nested maps. For example, if attribute "CSQ" holds a map of key-value
+   * pairs, then getValueAsString("CSQ", "Allele") returns the value of "Allele"
+   * in that map.
    * 
    * @param key
    * @return
    */
-  public String getValueAsString(String key)
+  public String getValueAsString(String... key)
   {
     if (otherDetails == null)
     {
       return null;
     }
-    Object value = otherDetails.get(key);
+    Object value = otherDetails.get(key[0]);
+    if (key.length > 1 && value instanceof Map<?, ?>)
+    {
+      value = ((Map) value).get(key[1]);
+    }
     return value == null ? null : value.toString();
   }
 
@@ -438,7 +433,7 @@ public class SequenceFeature implements FeatureLocationI
     {
       if (otherDetails == null)
       {
-        otherDetails = new HashMap<String, Object>();
+        otherDetails = new HashMap<>();
       }
 
       otherDetails.put(key, value);
@@ -463,8 +458,8 @@ public class SequenceFeature implements FeatureLocationI
               .getAttributeName(key);
     }
 
-    FeatureAttributes.getInstance().addAttribute(this.type, key, attDesc,
-            value.toString());
+    FeatureAttributes.getInstance().addAttribute(this.type, attDesc, value,
+            key);
   }
 
   /*
@@ -642,30 +637,33 @@ public class SequenceFeature implements FeatureLocationI
         {
           continue; // to avoid double reporting
         }
-        if (INFO_KEYS.containsKey(key))
+
+        Object value = entry.getValue();
+        if (value instanceof Map<?, ?>)
         {
           /*
-           * split selected INFO data by delimiter over multiple lines
+           * expand values in a Map attribute across separate lines
            */
-          String delimiter = INFO_KEYS.get(key);
-          String[] values = entry.getValue().toString().split(delimiter);
-          for (String value : values)
+          Map<?, ?> values = (Map<?, ?>) value;
+          for (Entry<?, ?> e : values.entrySet())
           {
-            sb.append(String.format(ROW_DATA, key, "", value));
+            sb.append(String.format(ROW_DATA, key, e.getKey().toString(), e
+                    .getValue().toString()));
           }
         }
         else
-        { // tried <td title="key"> but it failed to provide a tooltip :-(
+        {
+          // tried <td title="key"> but it failed to provide a tooltip :-(
           String attDesc = null;
           if (metadata != null)
           {
             attDesc = metadata.getAttributeName(key);
           }
-          String value = entry.getValue().toString();
-          if (isValueInteresting(key, value, metadata))
+          String s = entry.getValue().toString();
+          if (isValueInteresting(key, s, metadata))
           {
             sb.append(String.format(ROW_DATA, key, attDesc == null ? ""
-                    : attDesc, value));
+                    : attDesc, s));
           }
         }
       }
index 28be85f..c064373 100755 (executable)
@@ -450,17 +450,6 @@ public interface SequenceI extends ASequenceI
   public void transferAnnotation(SequenceI entry, Mapping mp);
 
   /**
-   * @param index
-   *          The sequence index in the MSA
-   */
-  public void setIndex(int index);
-
-  /**
-   * @return The index of the sequence in the alignment
-   */
-  public int getIndex();
-
-  /**
    * @return The RNA of the sequence in the alignment
    */
 
index 3dc4f19..7221d62 100644 (file)
@@ -2,9 +2,11 @@ package jalview.datamodel.features;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.TreeMap;
 
 /**
@@ -14,7 +16,45 @@ public class FeatureAttributes
 {
   private static FeatureAttributes instance = new FeatureAttributes();
 
-  private Map<String, Map<String, AttributeData>> attributes;
+  /*
+   * map, by feature type, of a map, by attribute name, of
+   * attribute description and min-max range (if known)
+   */
+  private Map<String, Map<String[], AttributeData>> attributes;
+
+  /*
+   * a case-insensitive comparator so that attributes are ordered e.g.
+   * AC
+   * af
+   * CSQ:AFR_MAF
+   * CSQ:Allele
+   */
+  private Comparator<String[]> comparator = new Comparator<String[]>()
+  {
+    @Override
+    public int compare(String[] o1, String[] o2)
+    {
+      int i = 0;
+      while (i < o1.length || i < o2.length)
+      {
+        if (o2.length <= i)
+        {
+          return o1.length <= i ? 0 : 1;
+        }
+        if (o1.length <= i)
+        {
+          return -1;
+        }
+        int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
+        if (comp != 0)
+        {
+          return comp;
+        }
+        i++;
+      }
+      return 0; // same length and all matched
+    }
+  };
 
   private class AttributeData
   {
@@ -116,17 +156,19 @@ public class FeatureAttributes
   }
 
   /**
-   * Answers the attributes known for the given feature type, in alphabetical
-   * order (not case sensitive), or an empty set if no attributes are known
+   * Answers the attribute names known for the given feature type, in
+   * alphabetical order (not case sensitive), or an empty set if no attributes
+   * are known. An attribute name is typically 'simple' e.g. "AC", but may be
+   * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
    * 
    * @param featureType
    * @return
    */
-  public List<String> getAttributes(String featureType)
+  public List<String[]> getAttributes(String featureType)
   {
     if (!attributes.containsKey(featureType))
     {
-      return Collections.<String> emptyList();
+      return Collections.<String[]> emptyList();
     }
 
     return new ArrayList<>(attributes.get(featureType).keySet());
@@ -156,23 +198,39 @@ public class FeatureAttributes
    * type, and updates the min-max for any numeric value
    * 
    * @param featureType
-   * @param attName
    * @param description
    * @param value
+   * @param attName
    */
-  public void addAttribute(String featureType, String attName,
-          String description, String value)
+  public void addAttribute(String featureType, String description,
+          Object value, String... attName)
   {
     if (featureType == null || attName == null)
     {
       return;
     }
 
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    /*
+     * if attribute value is a map, drill down one more level to
+     * record its sub-fields
+     */
+    if (value instanceof Map<?, ?>)
+    {
+      for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
+      {
+        String[] attNames = new String[attName.length + 1];
+        System.arraycopy(attName, 0, attNames, 0, attName.length);
+        attNames[attName.length] = entry.getKey().toString();
+        addAttribute(featureType, description, entry.getValue(), attNames);
+      }
+      return;
+    }
+
+    String valueAsString = value.toString();
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts == null)
     {
-      atts = new TreeMap<String, AttributeData>(
-              String.CASE_INSENSITIVE_ORDER);
+      atts = new TreeMap<>(comparator);
       attributes.put(featureType, atts);
     }
     AttributeData attData = atts.get(attName);
@@ -181,7 +239,7 @@ public class FeatureAttributes
       attData = new AttributeData();
       atts.put(attName, attData);
     }
-    attData.addInstance(description, value);
+    attData.addInstance(description, valueAsString);
   }
 
   /**
@@ -192,10 +250,10 @@ public class FeatureAttributes
    * @param attName
    * @return
    */
-  public String getDescription(String featureType, String attName)
+  public String getDescription(String featureType, String... attName)
   {
     String desc = null;
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts != null)
     {
       AttributeData attData = atts.get(attName);
@@ -217,9 +275,9 @@ public class FeatureAttributes
    * @param attName
    * @return
    */
-  public float[] getMinMax(String featureType, String attName)
+  public float[] getMinMax(String featureType, String... attName)
   {
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts != null)
     {
       AttributeData attData = atts.get(attName);
@@ -238,19 +296,18 @@ public class FeatureAttributes
    * @param attName
    * @param description
    */
-  public void addDescription(String featureType, String attName,
-          String description)
+  public void addDescription(String featureType, String description,
+          String... attName)
   {
     if (featureType == null || attName == null)
     {
       return;
     }
   
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts == null)
     {
-      atts = new TreeMap<String, AttributeData>(
-              String.CASE_INSENSITIVE_ORDER);
+      atts = new TreeMap<>(comparator);
       attributes.put(featureType, atts);
     }
     AttributeData attData = atts.get(attName);
index b40df50..bbd1f26 100644 (file)
@@ -44,11 +44,13 @@ public class EnsemblGenomes extends EnsemblGene
     return "EnsemblGenomes";
   }
 
-  private String Wrong[];
   @Override
   public String getTestQuery()
   {
-    return "DDB_G0283883";
+    /*
+     * Salmonella gene, Uniprot Q8Z9G6, EMBLCDS CAD01290
+     */
+    return "CAD01290";
   }
 
   @Override
index 99c0c51..974cc88 100644 (file)
@@ -145,4 +145,11 @@ public interface GFTSPanelI
    * @return
    */
   public String getCacheKey();
+
+  /**
+   * 
+   * @return user preference name for configuring this FTS search's autosearch
+   *         checkbox
+   */
+  public String getAutosearchPreference();
 }
index c0d005f..9802d4b 100644 (file)
@@ -56,6 +56,7 @@ import java.util.List;
 
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
+import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JFrame;
 import javax.swing.JInternalFrame;
@@ -88,6 +89,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
   protected JInternalFrame mainFrame = new JInternalFrame(
           getFTSFrameTitle());
 
+  protected JTabbedPane tabs = new JTabbedPane();
   protected IProgressIndicator progressIndicator;
 
   protected JComboBox<FTSDataColumnI> cmb_searchTarget = new JComboBox<FTSDataColumnI>();
@@ -98,6 +100,8 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   protected JButton btn_cancel = new JButton();
 
+  protected JCheckBox btn_autosearch = new JCheckBox();
+
   protected JvCacheableInputBox<String> txt_search;
 
   protected SequenceFetcher seqFetcher;
@@ -239,16 +243,38 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   public GFTSPanel()
   {
+    this(null);
+  }
+
+  public GFTSPanel(SequenceFetcher fetcher)
+  {
     try
     {
+      if (fetcher == null)
+      {
+        tabs = null;
+      }
       jbInit();
+      if (fetcher != null)
+      {
+        tabs.addTab(MessageManager.getString("label.retrieve_ids"),
+                fetcher);
+        fetcher.setDatabaseChooserVisible(false);
+        fetcher.embedWithFTSPanel(this);
+      }
       mainFrame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+      final JPanel ftsPanel = this;
       mainFrame.addFocusListener(new FocusAdapter()
       {
         @Override
         public void focusGained(FocusEvent e)
         {
-          txt_search.requestFocusInWindow();
+          // TODO: make selected tab gain focus in correct widget
+          if (tabs != null
+                  && tabs.getSelectedComponent() == ftsPanel)
+          {
+            txt_search.requestFocusInWindow();
+          }
         }
       });
       mainFrame.invalidate();
@@ -331,6 +357,20 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
       }
     });
 
+    btn_autosearch.setText(MessageManager.getString("option.autosearch"));
+    btn_autosearch.setToolTipText(
+            MessageManager.getString("option.enable_disable_autosearch"));
+    btn_autosearch.setSelected(
+            jalview.bin.Cache.getDefault(getAutosearchPreference(), true));
+    btn_autosearch.addActionListener(new java.awt.event.ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        jalview.bin.Cache.setProperty(getAutosearchPreference(),
+                Boolean.toString(btn_autosearch.isSelected()));
+      }
+    });
     btn_back.setFont(new java.awt.Font("Verdana", 0, 12));
     btn_back.setText(MessageManager.getString("action.back"));
     btn_back.addActionListener(new java.awt.event.ActionListener()
@@ -511,8 +551,14 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
                   if (primaryKeyName.equalsIgnoreCase(getCmbSearchTarget()
                           .getSelectedItem().toString()))
                   {
+                    // TODO: nicer to show the list in the result set before
+                    // viewing in Jalview perhaps ?
                     transferToSequenceFetcher(getTypedText());
                   }
+                  else
+                  {
+                    performSearchAction();
+                  }
                 }
               }
             });
@@ -522,12 +568,10 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
               @Override
               public void actionPerformed(ActionEvent e)
               {
-                String typed = getTypedText();
-                if (!typed.equalsIgnoreCase(lastSearchTerm))
+                if (btn_autosearch.isSelected()
+                        || txt_search.wasEnterPressed())
                 {
-                  searchAction(true);
-                  paginatorCart.clear();
-                  lastSearchTerm = typed;
+                  performSearchAction();
                 }
               }
             }, false);
@@ -549,6 +593,15 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
       }
     });
 
+    txt_search.addActionListener(new ActionListener()
+    {
+
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        performSearchAction();
+      }
+    });
     final String searchTabTitle = MessageManager
             .getString("label.search_result");
     final String configureCols = MessageManager
@@ -613,6 +666,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     pnl_results.add(tabbedPane);
     pnl_inputs.add(cmb_searchTarget);
     pnl_inputs.add(txt_search);
+    pnl_inputs.add(btn_autosearch);
     pnl_inputs.add(lbl_loading);
     pnl_inputs.add(lbl_warning);
     pnl_inputs.add(lbl_blank);
@@ -624,7 +678,17 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     this.add(pnl_results, java.awt.BorderLayout.CENTER);
     this.add(pnl_actions, java.awt.BorderLayout.SOUTH);
     mainFrame.setVisible(true);
-    mainFrame.setContentPane(this);
+    if (tabs != null)
+    {
+      tabs.setOpaque(true);
+      tabs.insertTab("Free Text Search", null, this, "", 0);
+      mainFrame.setContentPane(tabs);
+      tabs.setVisible(true);
+    }
+    else
+    {
+      mainFrame.setContentPane(this);
+    }
     mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
     mainFrame.addInternalFrameListener(
             new javax.swing.event.InternalFrameAdapter()
@@ -635,8 +699,6 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
                 closeAction();
               }
             });
-    mainFrame.setVisible(true);
-    mainFrame.setContentPane(this);
     mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
     Integer x = getTempUserPrefs().get("FTSPanel.x");
     Integer y = getTempUserPrefs().get("FTSPanel.y");
@@ -698,6 +760,18 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   }
 
+  void performSearchAction()
+  {
+    String typed = getTypedText();
+    if (typed != null && typed.length() > 0
+            && !typed.equalsIgnoreCase(lastSearchTerm))
+    {
+      searchAction(true);
+      paginatorCart.clear();
+      lastSearchTerm = typed;
+    }
+  }
+
   public boolean wantedFieldsUpdated()
   {
     if (previousWantedFields == null)
@@ -774,7 +848,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     }
   }
 
-  protected void btn_back_ActionPerformed()
+  public void btn_back_ActionPerformed()
   {
     closeAction();
     new SequenceFetcher(progressIndicator);
@@ -787,7 +861,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
     btn_cancel.setEnabled(false);
   }
 
-  protected void btn_cancel_ActionPerformed()
+  public void btn_cancel_ActionPerformed()
   {
     closeAction();
   }
index 19f7db4..053d91b 100644 (file)
@@ -43,9 +43,11 @@ public class PDBFTSPanel extends GFTSPanel
 
   private static final String PDB_FTS_CACHE_KEY = "CACHE.PDB_FTS";
 
+  private static final String PDB_AUTOSEARCH = "FTS.PDB.AUTOSEARCH";
+
   public PDBFTSPanel(SequenceFetcher fetcher)
   {
-    super();
+    super(fetcher);
     pageLimit = PDBFTSRestClient.getInstance().getDefaultResponsePageSize();
     this.seqFetcher = fetcher;
     this.progressIndicator = (fetcher == null) ? null
@@ -281,4 +283,9 @@ public class PDBFTSPanel extends GFTSPanel
     return PDB_FTS_CACHE_KEY;
   }
 
-}
+  @Override
+  public String getAutosearchPreference()
+  {
+    return PDB_AUTOSEARCH;
+  }
+}
\ No newline at end of file
index 020331a..df54dea 100644 (file)
@@ -44,9 +44,11 @@ public class UniprotFTSPanel extends GFTSPanel
 
   private static final String UNIPROT_FTS_CACHE_KEY = "CACHE.UNIPROT_FTS";
 
+  private static final String UNIPROT_AUTOSEARCH = "FTS.UNIPROT.AUTOSEARCH";
+
   public UniprotFTSPanel(SequenceFetcher fetcher)
   {
-    super();
+    super(fetcher);
     pageLimit = UniProtFTSRestClient.getInstance()
             .getDefaultResponsePageSize();
     this.seqFetcher = fetcher;
@@ -237,4 +239,10 @@ public class UniprotFTSPanel extends GFTSPanel
   {
     return UNIPROT_FTS_CACHE_KEY;
   }
+
+  @Override
+  public String getAutosearchPreference()
+  {
+    return UNIPROT_AUTOSEARCH;
+  }
 }
index 90271c8..4d09084 100644 (file)
@@ -583,58 +583,6 @@ public class AlignViewport extends AlignmentViewport
             .getStructureSelectionManager(Desktop.instance);
   }
 
-  /**
-   * 
-   * @param pdbEntries
-   * @return an array of SequenceI arrays, one for each PDBEntry, listing which
-   *         sequences in the alignment hold a reference to it
-   */
-  public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries)
-  {
-    List<SequenceI[]> seqvectors = new ArrayList<SequenceI[]>();
-    for (PDBEntry pdb : pdbEntries)
-    {
-      List<SequenceI> choosenSeqs = new ArrayList<SequenceI>();
-      for (SequenceI sq : alignment.getSequences())
-      {
-        Vector<PDBEntry> pdbRefEntries = sq.getDatasetSequence()
-                .getAllPDBEntries();
-        if (pdbRefEntries == null)
-        {
-          continue;
-        }
-        for (PDBEntry pdbRefEntry : pdbRefEntries)
-        {
-          if (pdbRefEntry.getId().equals(pdb.getId()))
-          {
-            if (pdbRefEntry.getChainCode() != null
-                    && pdb.getChainCode() != null)
-            {
-              if (pdbRefEntry.getChainCode().equalsIgnoreCase(
-                      pdb.getChainCode()) && !choosenSeqs.contains(sq))
-              {
-                choosenSeqs.add(sq);
-                continue;
-              }
-            }
-            else
-            {
-              if (!choosenSeqs.contains(sq))
-              {
-                choosenSeqs.add(sq);
-                continue;
-              }
-            }
-
-          }
-        }
-      }
-      seqvectors
-              .add(choosenSeqs.toArray(new SequenceI[choosenSeqs.size()]));
-    }
-    return seqvectors.toArray(new SequenceI[seqvectors.size()][]);
-  }
-
   @Override
   public boolean isNormaliseSequenceLogo()
   {
index 6280a64..da3819c 100644 (file)
@@ -57,8 +57,16 @@ import javax.swing.event.ChangeListener;
 
 public class FeatureColourChooser extends JalviewDialog
 {
+  private static final String COLON = ":";
+
   private static final int MAX_TOOLTIP_LENGTH = 50;
 
+  private static int NO_COLOUR_OPTION = 0;
+
+  private static int MIN_COLOUR_OPTION = 1;
+
+  private static int MAX_COLOUR_OPTION = 2;
+
   private FeatureRenderer fr;
 
   private FeatureColourI cs;
@@ -157,9 +165,8 @@ public class FeatureColourChooser extends JalviewDialog
     this.fr = frender;
     this.type = theType;
     ap = fr.ap;
-    String title = MessageManager
-            .formatMessage("label.graduated_color_for_params", new String[]
-            { theType });
+    String title = MessageManager.formatMessage("label.variable_color_for",
+            new String[] { theType });
     initDialogFrame(this, true, blocking, title, 470, 300);
 
     slider.addChangeListener(new ChangeListener()
@@ -190,6 +197,7 @@ public class FeatureColourChooser extends JalviewDialog
       }
     });
 
+    // todo move all threshold setup inside a method
     float mm[] = fr.getMinMax().get(theType)[0];
     min = mm[0];
     max = mm[1];
@@ -231,6 +239,8 @@ public class FeatureColourChooser extends JalviewDialog
     }
     minColour.setBackground(oldminColour = cs.getMinColour());
     maxColour.setBackground(oldmaxColour = cs.getMaxColour());
+    noColour = cs.getNoColour();
+
     adjusting = true;
 
     try
@@ -245,15 +255,15 @@ public class FeatureColourChooser extends JalviewDialog
     /*
      * set the initial state of options on screen
      */
-    thresholdIsMin.setSelected(!cs.isAutoScaled());
-
     if (cs.isColourByLabel())
     {
       if (cs.isColourByAttribute())
       {
         byAttributeText.setSelected(true);
         textAttributeCombo.setEnabled(true);
-        textAttributeCombo.setSelectedItem(cs.getAttributeName());
+        String[] attributeName = cs.getAttributeName();
+        textAttributeCombo
+                .setSelectedItem(String.join(COLON, attributeName));
       }
       else
       {
@@ -266,8 +276,9 @@ public class FeatureColourChooser extends JalviewDialog
       if (cs.isColourByAttribute())
       {
         byAttributeValue.setSelected(true);
-        String attributeName = cs.getAttributeName();
-        valueAttributeCombo.setSelectedItem(attributeName);
+        String[] attributeName = cs.getAttributeName();
+        valueAttributeCombo
+                .setSelectedItem(String.join(COLON, attributeName));
         valueAttributeCombo.setEnabled(true);
         updateMinMax();
       }
@@ -278,6 +289,19 @@ public class FeatureColourChooser extends JalviewDialog
       }
     }
 
+    if (noColour == null)
+    {
+      noValueCombo.setSelectedIndex(NO_COLOUR_OPTION);
+    }
+    else if (noColour.equals(oldminColour))
+    {
+      noValueCombo.setSelectedIndex(MIN_COLOUR_OPTION);
+    }
+    else if (noColour.equals(oldmaxColour))
+    {
+      noValueCombo.setSelectedIndex(MAX_COLOUR_OPTION);
+    }
+
     threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
     threshline.value = cs.getThreshold();
 
@@ -369,7 +393,8 @@ public class FeatureColourChooser extends JalviewDialog
     else if (byAttributeValue.isSelected())
     {
       String attName = (String) valueAttributeCombo.getSelectedItem();
-      minMax = FeatureAttributes.getInstance().getMinMax(type, attName);
+      String[] attNames = attName.split(COLON);
+      minMax = FeatureAttributes.getInstance().getMinMax(type, attNames);
     }
     if (minMax != null)
     {
@@ -408,8 +433,8 @@ public class FeatureColourChooser extends JalviewDialog
     byAttributeValue.addActionListener(changeMinMaxAction);
     byWhatPanel.add(byAttributeValue);
 
-    List<String> attNames = FeatureAttributes.getInstance().getAttributes(
-            type);
+    List<String[]> attNames = FeatureAttributes.getInstance()
+            .getAttributes(type);
     valueAttributeCombo = populateAttributesDropdown(type, attNames, true);
 
     /*
@@ -555,21 +580,21 @@ public class FeatureColourChooser extends JalviewDialog
   }
 
   /**
-   * Action on user choice of no / min / max colour when there is no value to
-   * colour by
+   * Action on user choice of no / min / max colour to use when there is no
+   * value to colour by
    */
   protected void setNoValueColour()
   {
     int i = noValueCombo.getSelectedIndex();
-    if (i == 0)
+    if (i == NO_COLOUR_OPTION)
     {
       noColour = null;
     }
-    else if (i == 1)
+    else if (i == MIN_COLOUR_OPTION)
     {
       noColour = minColour.getBackground();
     }
-    else if (i == 2)
+    else if (i == MAX_COLOUR_OPTION)
     {
       noColour = maxColour.getBackground();
     }
@@ -612,8 +637,8 @@ public class FeatureColourChooser extends JalviewDialog
     byAttributeText.addActionListener(changeColourAction);
     byTextPanel.add(byAttributeText);
 
-    List<String> attNames = FeatureAttributes.getInstance().getAttributes(
-            type);
+    List<String[]> attNames = FeatureAttributes.getInstance()
+            .getAttributes(type);
     textAttributeCombo = populateAttributesDropdown(type, attNames, false);
     byTextPanel.add(textAttributeCombo);
 
@@ -715,13 +740,18 @@ public class FeatureColourChooser extends JalviewDialog
     {
       attribute = (String) textAttributeCombo.getSelectedItem();
       textAttributeCombo.setEnabled(true);
+      acg.setAttributeName(attribute.split(COLON));
     }
     else if (byAttributeValue.isSelected())
     {
       attribute = (String) valueAttributeCombo.getSelectedItem();
       valueAttributeCombo.setEnabled(true);
+      acg.setAttributeName(attribute.split(COLON));
+    }
+    else
+    {
+      acg.setAttributeName((String) null);
     }
-    acg.setAttributeName(attribute);
 
     if (!hasThreshold)
     {
@@ -798,6 +828,7 @@ public class FeatureColourChooser extends JalviewDialog
       maxColour.setForeground(oldmaxColour);
       minColour.setBackground(oldminColour);
       minColour.setForeground(oldminColour);
+      noColour = oldNoColour;
     }
 
     /*
@@ -912,23 +943,26 @@ public class FeatureColourChooser extends JalviewDialog
   /**
    * A helper method to build the drop-down choice of attributes for a feature.
    * Where metadata is available with a description for an attribute, that is
-   * added as a tooltip. The list may be restricted to attributes for which we
-   * hold a range of numerical values (so suitable candidates for a graduated
-   * colour scheme).
+   * added as a tooltip. The list may optionally be restricted to attributes for
+   * which we hold a range of numerical values (so suitable candidates for a
+   * graduated colour scheme).
+   * <p>
+   * Attribute names may be 'simple' e.g. "AC" or 'compound' e.g. {"CSQ",
+   * "Allele"}. Compound names are rendered for display as (e.g.) CSQ:Allele.
    * 
    * @param featureType
    * @param attNames
    * @param withNumericRange
    */
   protected JComboBox<String> populateAttributesDropdown(
-          String featureType, List<String> attNames,
+          String featureType, List<String[]> attNames,
           boolean withNumericRange)
   {
     List<String> validAtts = new ArrayList<>();
     List<String> tooltips = new ArrayList<>();
 
     FeatureAttributes fa = FeatureAttributes.getInstance();
-    for (String attName : attNames)
+    for (String[] attName : attNames)
     {
       if (withNumericRange)
       {
@@ -938,7 +972,7 @@ public class FeatureColourChooser extends JalviewDialog
           continue;
         }
       }
-      validAtts.add(attName);
+      validAtts.add(String.join(COLON, attName));
       String desc = fa.getDescription(featureType, attName);
       if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
       {
index 4e4d2cb..ed98830 100644 (file)
@@ -53,6 +53,7 @@ import java.awt.FlowLayout;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.GridLayout;
+import java.awt.LayoutManager;
 import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -119,6 +120,8 @@ import javax.swing.table.TableCellRenderer;
 public class FeatureSettings extends JPanel
         implements FeatureSettingsControllerI
 {
+  private static final String COLON = ":";
+
   private static final int MIN_WIDTH = 400;
 
   private static final int MIN_HEIGHT = 400;
@@ -199,6 +202,9 @@ public class FeatureSettings extends JPanel
 
   private JTextArea filtersAsText;
 
+  // set white normally, black to debug layout
+  private Color debugBorderColour = Color.white;
+
   /**
    * Constructor
    * 
@@ -1371,6 +1377,7 @@ public class FeatureSettings extends JPanel
      */
     JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
     andOrPanel.setBackground(Color.white);
+    andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour));
     andFilters = new JRadioButton("And");
     orFilters = new JRadioButton("Or");
     ActionListener actionListener = new ActionListener()
@@ -1397,8 +1404,9 @@ public class FeatureSettings extends JPanel
      * panel with filters - populated by refreshFiltersDisplay
      */
     chooseFiltersPanel = new JPanel();
-    chooseFiltersPanel.setLayout(new BoxLayout(chooseFiltersPanel,
-            BoxLayout.Y_AXIS));
+    LayoutManager box = new BoxLayout(chooseFiltersPanel,
+            BoxLayout.Y_AXIS);
+    chooseFiltersPanel.setLayout(box);
     filtersPanel.add(chooseFiltersPanel);
 
     /*
@@ -1454,12 +1462,12 @@ public class FeatureSettings extends JPanel
     }
     if (!found)
     {
-      filteredFeatureChoice // todo i18n
-              .addItem("No filterable feature attributes known");
+      filteredFeatureChoice.addItem(MessageManager
+              .getString("label.no_feature_attributes"));
+      filteredFeatureChoice.setEnabled(false);
     }
 
     filteredFeatureChoice.addItemListener(listener);
-
   }
 
   /**
@@ -1480,8 +1488,8 @@ public class FeatureSettings extends JPanel
      * look up attributes known for feature type
      */
     String selectedType = (String) filteredFeatureChoice.getSelectedItem();
-    List<String> attNames = FeatureAttributes.getInstance().getAttributes(
-            selectedType);
+    List<String[]> attNames = FeatureAttributes.getInstance()
+            .getAttributes(selectedType);
 
     /*
      * if this feature type has filters set, load them first
@@ -1501,7 +1509,8 @@ public class FeatureSettings extends JPanel
     /*
      * and an empty filter for the user to populate (add)
      */
-    KeyedMatcherI noFilter = new KeyedMatcher("", Condition.values()[0], "");
+    KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "",
+            (String) null);
     filters.add(noFilter);
 
     /*
@@ -1510,14 +1519,16 @@ public class FeatureSettings extends JPanel
     int filterIndex = 0;
     for (KeyedMatcherI filter : filters)
     {
-      String key = filter.getKey();
+      String[] attName = filter.getKey();
       Condition condition = filter.getMatcher()
               .getCondition();
       String pattern = filter.getMatcher().getPattern();
-      JPanel row = addFilter(key, attNames, condition, pattern, filterIndex);
+      JPanel row = addFilter(attName, attNames, condition, pattern, filterIndex);
+      row.setBorder(BorderFactory.createLineBorder(debugBorderColour));
       chooseFiltersPanel.add(row);
       filterIndex++;
     }
+    // chooseFiltersPanel.add(Box.createVerticalGlue());
 
     filtersPane.validate();
     filtersPane.repaint();
@@ -1531,27 +1542,24 @@ public class FeatureSettings extends JPanel
    * <li>a text field for input of a match pattern</li>
    * <li>optionally, a 'remove' button</li>
    * </ul>
-   * If attribute, condition or pattern are not null, they are set as defaults
-   * for the input fields. The 'remove' button is added unless the pattern is
-   * null or empty (incomplete filter condition).
+   * If attribute, condition or pattern are not null, they are set as defaults for
+   * the input fields. The 'remove' button is added unless the pattern is null or
+   * empty (incomplete filter condition).
    * 
-   * @param attribute
+   * @param attName
    * @param attNames
    * @param cond
    * @param pattern
    * @param filterIndex
    * @return
    */
-  protected JPanel addFilter(String attribute, List<String> attNames,
+  protected JPanel addFilter(String[] attName, List<String[]> attNames,
           Condition cond, String pattern, int filterIndex)
   {
     JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
     filterRow.setBackground(Color.white);
 
     /*
-     * inputs for attribute, condition, pattern
-     */
-    /*
      * drop-down choice of attribute, with description as a tooltip 
      * if we can obtain it
      */
@@ -1588,13 +1596,13 @@ public class FeatureSettings extends JPanel
       }
     };
 
-    if ("".equals(attribute))
+    if (attName == null) // the 'add a condition' row
     {
       attCombo.setSelectedItem(null);
     }
     else
     {
-      attCombo.setSelectedItem(attribute);
+      attCombo.setSelectedItem(String.join(COLON, attName));
     }
     attCombo.addItemListener(itemListener);
 
@@ -1634,7 +1642,7 @@ public class FeatureSettings extends JPanel
      */
     if (pattern != null && pattern.trim().length() > 0)
     {
-      // todo: gif for - button
+      // todo: gif for button drawing '-' or 'x'
       JButton removeCondition = new BasicArrowButton(SwingConstants.WEST);
       removeCondition.setToolTipText(MessageManager
               .getString("label.delete_row"));
@@ -1662,22 +1670,24 @@ public class FeatureSettings extends JPanel
    * @param attNames
    */
   protected JComboBox<String> populateAttributesDropdown(
-          String featureType, List<String> attNames)
+          String featureType, List<String[]> attNames)
   {
+    List<String> displayNames = new ArrayList<>();
     List<String> tooltips = new ArrayList<>();
     FeatureAttributes fa = FeatureAttributes.getInstance();
-    for (String attName : attNames)
+    for (String[] attName : attNames)
     {
       String desc = fa.getDescription(featureType, attName);
       if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
       {
         desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
       }
+      displayNames.add(String.join(COLON, attName));
       tooltips.add(desc == null ? "" : desc);
     }
 
     JComboBox<String> attCombo = JvSwingUtils.buildComboWithTooltips(
-            attNames, tooltips);
+            displayNames, tooltips);
     if (attNames.isEmpty())
     {
       attCombo.setToolTipText(MessageManager
@@ -1755,7 +1765,8 @@ public class FeatureSettings extends JPanel
     String attName = (String) attCombo.getSelectedItem();
     Condition cond = (Condition) condCombo.getSelectedItem();
     String pattern = valueField.getText();
-    KeyedMatcherI km = new KeyedMatcher(attName, cond, pattern);
+    KeyedMatcherI km = new KeyedMatcher(cond, pattern,
+            attName.split(COLON));
 
     filters.set(filterIndex, km);
   }
@@ -2129,7 +2140,7 @@ public class FeatureSettings extends JPanel
 
     if (gcol.isColourByAttribute())
     {
-      tx.append(gcol.getAttributeName());
+      tx.append(String.join(":", gcol.getAttributeName()));
     }
     else if (!gcol.isColourByLabel())
     {
index 7371eb5..2991889 100644 (file)
@@ -183,6 +183,8 @@ public class OverviewCanvas extends JComponent
   @Override
   public void paintComponent(Graphics g)
   {
+    // super.paintComponent(g);
+
     if (restart)
     {
       if (lastMiniMe == null)
@@ -204,7 +206,8 @@ public class OverviewCanvas extends JComponent
               && ((getWidth() != od.getWidth())
                       || (getHeight() != od.getHeight())))
       {
-        // if there is annotation, scale the alignment and annotation separately
+        // if there is annotation, scale the alignment and annotation
+        // separately
         if (od.getGraphHeight() > 0)
         {
           BufferedImage topImage = lastMiniMe.getSubimage(0, 0,
@@ -235,25 +238,24 @@ public class OverviewCanvas extends JComponent
           od.setHeight(getHeight());
         }
 
-        // scale lastMiniMe to the new size
-        g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
-
         // make sure the box is in the right place
         od.setBoxPosition(av.getAlignment().getHiddenSequences(),
                 av.getAlignment().getHiddenColumns());
       }
-      else // not a resize
-      {
-        // fall back to normal behaviour
-        g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
-      }
+      // fall back to normal behaviour
+      g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
     }
-
+    else
+    {
+      g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
+    }
+    
     // draw the box
     g.setColor(Color.red);
     od.drawBox(g);
   }
 
+
   public void dispose()
   {
     dispose = true;
index 9ddb751..43b4310 100755 (executable)
@@ -329,6 +329,22 @@ public class OverviewPanel extends JPanel
    * changed
    * 
    */
+  private void setBoxPositionOnly()
+  {
+    if (od != null)
+    {
+      int oldX = od.getBoxX();
+      int oldY = od.getBoxY();
+      int oldWidth = od.getBoxWidth();
+      int oldHeight = od.getBoxHeight();
+      od.setBoxPosition(av.getAlignment().getHiddenSequences(),
+              av.getAlignment().getHiddenColumns());
+      repaint(oldX - 1, oldY - 1, oldWidth + 2, oldHeight + 2);
+      repaint(od.getBoxX(), od.getBoxY(), od.getBoxWidth(),
+              od.getBoxHeight());
+    }
+  }
+
   private void setBoxPosition()
   {
     if (od != null)
@@ -342,7 +358,7 @@ public class OverviewPanel extends JPanel
   @Override
   public void propertyChange(PropertyChangeEvent evt)
   {
-    setBoxPosition();
+    setBoxPositionOnly();
   }
 
   /**
index e05230b..8d46792 100755 (executable)
@@ -26,6 +26,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.fts.core.GFTSPanel;
 import jalview.fts.service.pdb.PDBFTSPanel;
 import jalview.fts.service.uniprot.UniprotFTSPanel;
 import jalview.io.FileFormatI;
@@ -78,6 +79,8 @@ public class SequenceFetcher extends JPanel implements Runnable
 
   JButton close = new JButton();
 
+  JButton back = new JButton();
+
   JPanel jPanel1 = new JPanel();
 
   JTextArea textArea = new JTextArea();
@@ -383,6 +386,15 @@ public class SequenceFetcher extends JPanel implements Runnable
                     .getString("label.additional_sequence_fetcher"));
   }
 
+  GFTSPanel parentFTSframe = null;
+  /**
+   * change the buttons so they fit with the FTS panel.
+   */
+  public void embedWithFTSPanel(GFTSPanel toClose)
+  {
+    back.setVisible(true);
+    parentFTSframe = toClose;
+  }
   private void jbInit() throws Exception
   {
     this.setLayout(borderLayout2);
@@ -427,7 +439,7 @@ public class SequenceFetcher extends JPanel implements Runnable
         example_actionPerformed();
       }
     });
-    close.setText(MessageManager.getString("action.close"));
+    close.setText(MessageManager.getString("action.cancel"));
     close.addActionListener(new ActionListener()
     {
       @Override
@@ -436,6 +448,17 @@ public class SequenceFetcher extends JPanel implements Runnable
         close_actionPerformed(e);
       }
     });
+    back.setText(MessageManager.getString("action.back"));
+    back.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        parentFTSframe.btn_back_ActionPerformed();
+      }
+    });
+    // back not visible unless embedded
+    back.setVisible(false);
     textArea.setFont(JvSwingUtils.getLabelFont());
     textArea.setLineWrap(true);
     textArea.addKeyListener(new KeyAdapter()
@@ -451,9 +474,10 @@ public class SequenceFetcher extends JPanel implements Runnable
     });
     jPanel3.setLayout(borderLayout1);
     borderLayout1.setVgap(5);
-    jPanel1.add(ok);
+    jPanel1.add(back);
     jPanel1.add(example);
     jPanel1.add(clear);
+    jPanel1.add(ok);
     jPanel1.add(close);
     jPanel2.setLayout(borderLayout3);
     databaseButt = /*database.getDatabaseSelectorButton();
@@ -582,6 +606,10 @@ public class SequenceFetcher extends JPanel implements Runnable
     try
     {
       frame.setClosed(true);
+      if (parentFTSframe!=null)
+      {
+        parentFTSframe.btn_cancel_ActionPerformed();
+      }
     } catch (Exception ex)
     {
     }
@@ -594,7 +622,7 @@ public class SequenceFetcher extends JPanel implements Runnable
     textArea.setEnabled(false);
     ok.setEnabled(false);
     close.setEnabled(false);
-
+    back.setEnabled(false);
     Thread worker = new Thread(this);
     worker.start();
   }
@@ -606,6 +634,7 @@ public class SequenceFetcher extends JPanel implements Runnable
     textArea.setEnabled(true);
     ok.setEnabled(true);
     close.setEnabled(true);
+    back.setEnabled(parentFTSframe != null);
   }
 
   @Override
@@ -1087,4 +1116,9 @@ public class SequenceFetcher extends JPanel implements Runnable
   {
     frame.setVisible(false);
   }
+
+  public void setDatabaseChooserVisible(boolean b)
+  {
+    databaseButt.setVisible(b);
+  }
 }
index 20f4a49..37632ef 100644 (file)
@@ -927,16 +927,10 @@ public class StructureChooser extends GStructureChooser
     }
     if (pdbEntriesToView.length > 1)
     {
-      ArrayList<SequenceI[]> seqsMap = new ArrayList<>();
-      for (SequenceI seq : sequences)
-      {
-        seqsMap.add(new SequenceI[] { seq });
-      }
-      SequenceI[][] collatedSeqs = seqsMap.toArray(new SequenceI[0][0]);
-
-      setProgressBar(MessageManager
-                    .getString("status.fetching_3d_structures_for_selected_entries"), progressId);
-      sViewer.viewStructures(pdbEntriesToView, collatedSeqs, alignPanel);
+      setProgressBar(MessageManager.getString(
+              "status.fetching_3d_structures_for_selected_entries"),
+              progressId);
+      sViewer.viewStructures(pdbEntriesToView, sequences, alignPanel);
     }
     else
     {
index e58b378..b142613 100644 (file)
@@ -29,19 +29,24 @@ import jalview.structure.StructureSelectionManager;
 
 import java.awt.Rectangle;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 /**
- * proxy for handling structure viewers.
- * 
- * this allows new views to be created with the currently configured viewer, the
- * preferred viewer to be set/read and existing views created previously with a
- * particular viewer to be recovered
+ * A proxy for handling structure viewers, that orchestrates adding selected
+ * structures, associated with sequences in Jalview, to an existing viewer, or
+ * opening a new one. Currently supports either Jmol or Chimera as the structure
+ * viewer.
  * 
  * @author jprocter
  */
 public class StructureViewer
 {
+  private static final String UNKNOWN_VIEWER_TYPE = "Unknown structure viewer type ";
+
   StructureSelectionManager ssm;
 
   public enum ViewerType
@@ -49,6 +54,16 @@ public class StructureViewer
     JMOL, CHIMERA
   };
 
+  /**
+   * Constructor
+   * 
+   * @param structureSelectionManager
+   */
+  public StructureViewer(StructureSelectionManager structureSelectionManager)
+  {
+    ssm = structureSelectionManager;
+  }
+
   public ViewerType getViewerType()
   {
     String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
@@ -61,135 +76,157 @@ public class StructureViewer
     Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
   }
 
-  public StructureViewer(
-          StructureSelectionManager structureSelectionManager)
-  {
-    ssm = structureSelectionManager;
-  }
-
   /**
    * View multiple PDB entries, each with associated sequences
    * 
    * @param pdbs
-   * @param seqsForPdbs
+   * @param seqs
    * @param ap
    * @return
    */
   public JalviewStructureDisplayI viewStructures(PDBEntry[] pdbs,
-          SequenceI[][] seqsForPdbs, AlignmentPanel ap)
+          SequenceI[] seqs, AlignmentPanel ap)
   {
-    JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqsForPdbs, ap);
+    JalviewStructureDisplayI viewer = onlyOnePdb(pdbs, seqs, ap);
     if (viewer != null)
     {
+      /*
+       * user added structure to an existing viewer - all done
+       */
       return viewer;
     }
-    return viewStructures(getViewerType(), pdbs, seqsForPdbs, ap);
+
+    ViewerType viewerType = getViewerType();
+
+    Map<PDBEntry, SequenceI[]> seqsForPdbs = getSequencesForPdbs(pdbs,
+            seqs);
+    PDBEntry[] pdbsForFile = seqsForPdbs.keySet().toArray(
+            new PDBEntry[seqsForPdbs.size()]);
+    SequenceI[][] theSeqs = seqsForPdbs.values().toArray(
+            new SequenceI[seqsForPdbs.size()][]);
+    JalviewStructureDisplayI sview = null;
+    if (viewerType.equals(ViewerType.JMOL))
+    {
+      sview = new AppJmol(ap, pdbsForFile, theSeqs);
+    }
+    else if (viewerType.equals(ViewerType.CHIMERA))
+    {
+      sview = new ChimeraViewFrame(pdbsForFile, theSeqs, ap);
+    }
+    else
+    {
+      Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
+    }
+    return sview;
   }
 
   /**
-   * A strictly temporary method pending JAL-1761 refactoring. Determines if all
-   * the passed PDB entries are the same (this is the case if selected sequences
-   * to view structure for are chains of the same structure). If so, calls the
-   * single-pdb version of viewStructures and returns the viewer, else returns
-   * null.
+   * Converts the list of selected PDB entries (possibly including duplicates
+   * for multiple chains), and corresponding sequences, into a map of sequences
+   * for each distinct PDB file. Returns null if either argument is null, or
+   * their lengths do not match.
    * 
    * @param pdbs
-   * @param seqsForPdbs
-   * @param ap
+   * @param seqs
    * @return
    */
-  private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
-          SequenceI[][] seqsForPdbs, AlignmentPanel ap)
+  Map<PDBEntry, SequenceI[]> getSequencesForPdbs(PDBEntry[] pdbs,
+          SequenceI[] seqs)
   {
-    List<SequenceI> seqs = new ArrayList<SequenceI>();
-    if (pdbs == null || pdbs.length == 0)
+    if (pdbs == null || seqs == null || pdbs.length != seqs.length)
     {
       return null;
     }
-    int i = 0;
-    String firstFile = pdbs[0].getFile();
-    for (PDBEntry pdb : pdbs)
+
+    /*
+     * we want only one 'representative' PDBEntry per distinct file name
+     * (there may be entries for distinct chains)
+     */
+    Map<String, PDBEntry> pdbsSeen = new HashMap<>();
+
+    /*
+     * LinkedHashMap preserves order of PDB entries (significant if they
+     * will get superimposed to the first structure)
+     */
+    Map<PDBEntry, List<SequenceI>> pdbSeqs = new LinkedHashMap<>();
+    for (int i = 0; i < pdbs.length; i++)
     {
+      PDBEntry pdb = pdbs[i];
+      SequenceI seq = seqs[i];
       String pdbFile = pdb.getFile();
-      if (pdbFile == null || !pdbFile.equals(firstFile))
+      if (!pdbsSeen.containsKey(pdbFile))
       {
-        return null;
+        pdbsSeen.put(pdbFile, pdb);
+        pdbSeqs.put(pdb, new ArrayList<SequenceI>());
+      }
+      else
+      {
+        pdb = pdbsSeen.get(pdbFile);
       }
-      SequenceI[] pdbseqs = seqsForPdbs[i++];
-      if (pdbseqs != null)
+      List<SequenceI> seqsForPdb = pdbSeqs.get(pdb);
+      if (!seqsForPdb.contains(seq))
       {
-        for (SequenceI sq : pdbseqs)
-        {
-          seqs.add(sq);
-        }
+        seqsForPdb.add(seq);
       }
     }
-    return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
-            ap);
-  }
-
-  public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
-          SequenceI[] seqsForPdb, AlignmentPanel ap)
-  {
-    return viewStructures(getViewerType(), pdb, seqsForPdb, ap);
-  }
 
-  protected JalviewStructureDisplayI viewStructures(ViewerType viewerType,
-          PDBEntry[] pdbs, SequenceI[][] seqsForPdbs, AlignmentPanel ap)
-  {
-    PDBEntry[] pdbsForFile = getUniquePdbFiles(pdbs);
-    JalviewStructureDisplayI sview = null;
-    if (viewerType.equals(ViewerType.JMOL))
-    {
-      sview = new AppJmol(ap, pdbsForFile,
-              ap.av.collateForPDB(pdbsForFile));
-    }
-    else if (viewerType.equals(ViewerType.CHIMERA))
+    /*
+     * convert to Map<PDBEntry, SequenceI[]>
+     */
+    Map<PDBEntry, SequenceI[]> result = new LinkedHashMap<>();
+    for (Entry<PDBEntry, List<SequenceI>> entry : pdbSeqs.entrySet())
     {
-      sview = new ChimeraViewFrame(pdbsForFile,
-              ap.av.collateForPDB(pdbsForFile), ap);
+      List<SequenceI> theSeqs = entry.getValue();
+      result.put(entry.getKey(),
+              theSeqs.toArray(new SequenceI[theSeqs.size()]));
     }
-    else
-    {
-      Cache.log.error("Unknown structure viewer type "
-              + getViewerType().toString());
-    }
-    return sview;
+
+    return result;
   }
 
   /**
-   * Convert the array of PDBEntry into an array with no filename repeated
+   * A strictly temporary method pending JAL-1761 refactoring. Determines if all
+   * the passed PDB entries are the same (this is the case if selected sequences
+   * to view structure for are chains of the same structure). If so, calls the
+   * single-pdb version of viewStructures and returns the viewer, else returns
+   * null.
    * 
    * @param pdbs
+   * @param seqsForPdbs
+   * @param ap
    * @return
    */
-  static PDBEntry[] getUniquePdbFiles(PDBEntry[] pdbs)
+  private JalviewStructureDisplayI onlyOnePdb(PDBEntry[] pdbs,
+          SequenceI[] seqsForPdbs, AlignmentPanel ap)
   {
-    if (pdbs == null)
+    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    if (pdbs == null || pdbs.length == 0)
     {
       return null;
     }
-    List<PDBEntry> uniques = new ArrayList<PDBEntry>();
-    List<String> filesSeen = new ArrayList<String>();
-    for (PDBEntry entry : pdbs)
+    int i = 0;
+    String firstFile = pdbs[0].getFile();
+    for (PDBEntry pdb : pdbs)
     {
-      String file = entry.getFile();
-      if (file == null)
+      String pdbFile = pdb.getFile();
+      if (pdbFile == null || !pdbFile.equals(firstFile))
       {
-        uniques.add(entry);
+        return null;
       }
-      else if (!filesSeen.contains(file))
+      SequenceI pdbseq = seqsForPdbs[i++];
+      if (pdbseq != null)
       {
-        uniques.add(entry);
-        filesSeen.add(file);
+        seqs.add(pdbseq);
       }
     }
-    return uniques.toArray(new PDBEntry[uniques.size()]);
+    return viewStructures(pdbs[0], seqs.toArray(new SequenceI[seqs.size()]),
+            ap);
   }
 
-  protected JalviewStructureDisplayI viewStructures(ViewerType viewerType,
-          PDBEntry pdb, SequenceI[] seqsForPdb, AlignmentPanel ap)
+  public JalviewStructureDisplayI viewStructures(PDBEntry pdb,
+          SequenceI[] seqsForPdb, AlignmentPanel ap)
   {
+    ViewerType viewerType = getViewerType();
     JalviewStructureDisplayI sview = null;
     if (viewerType.equals(ViewerType.JMOL))
     {
@@ -201,8 +238,7 @@ public class StructureViewer
     }
     else
     {
-      Cache.log.error("Unknown structure viewer type "
-              + getViewerType().toString());
+      Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
     }
     return sview;
   }
@@ -242,7 +278,7 @@ public class StructureViewer
               "Unsupported structure viewer type " + type.toString());
       break;
     default:
-      Cache.log.error("Unknown structure viewer type " + type.toString());
+      Cache.log.error(UNKNOWN_VIEWER_TYPE + type.toString());
     }
     return sview;
   }
index a603cca..2340283 100755 (executable)
@@ -174,11 +174,6 @@ public abstract class AlignFile extends FileParse
     }
     parseCalled = true;
     parse();
-    // sets the index of each sequence in the alignment
-    for (int i = 0, c = seqs.size(); i < c; i++)
-    {
-      seqs.get(i).setIndex(i);
-    }
   }
 
   /**
index 1f92428..6b82671 100644 (file)
@@ -210,11 +210,12 @@ public class SequenceAnnotationReport
         FeatureColourI fc = fr.getFeatureColours().get(feature.getType());
         if (fc != null && fc.isColourByAttribute())
         {
-          String attName = fc.getAttributeName();
+          String[] attName = fc.getAttributeName();
           String attVal = feature.getValueAsString(attName);
           if (attVal != null)
           {
-            sb.append("; ").append(attName).append("=").append(attVal);
+            sb.append("; ").append(String.join(":", attName)).append("=")
+                    .append(attVal);
           }
         }
       }
@@ -301,7 +302,7 @@ public class SequenceAnnotationReport
    */
   Collection<List<String>> createLinksFrom(SequenceI seq, String link)
   {
-    Map<String, List<String>> urlSets = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> urlSets = new LinkedHashMap<>();
     UrlLink urlLink = new UrlLink(link);
     if (!urlLink.isValid())
     {
index 3d0daed..a837512 100644 (file)
@@ -28,6 +28,7 @@ import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -70,11 +71,49 @@ public class JvCacheableInputBox<E> extends JComboBox<String>
 
   private JMenuItem menuItemClearCache = new JMenuItem();
 
+  volatile boolean enterWasPressed = false;
+
+  /**
+   * @return flag indicating if the most recent keypress was enter
+   */
+  public boolean wasEnterPressed()
+  {
+    return enterWasPressed;
+  }
+
   public JvCacheableInputBox(String newCacheKey)
   {
     super();
     this.cacheKey = newCacheKey;
     setEditable(true);
+    addKeyListener(new KeyListener()
+    {
+
+      @Override
+      public void keyTyped(KeyEvent e)
+      {
+        enterWasPressed = false;
+        if (e.getKeyCode() == KeyEvent.VK_ENTER)
+        {
+          enterWasPressed = true;
+        }
+        // let event bubble up
+      }
+
+      @Override
+      public void keyReleased(KeyEvent e)
+      {
+        // TODO Auto-generated method stub
+
+      }
+
+      @Override
+      public void keyPressed(KeyEvent e)
+      {
+        // TODO Auto-generated method stub
+
+      }
+    });
     setPrototypeDisplayValue(
             "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
     appCache = AppCache.getInstance();
index 5adc55c..2847bd7 100644 (file)
@@ -1,17 +1,9 @@
 package jalview.io.vcf;
 
-import htsjdk.samtools.util.CloseableIterator;
-import htsjdk.variant.variantcontext.Allele;
-import htsjdk.variant.variantcontext.VariantContext;
-import htsjdk.variant.vcf.VCFHeader;
-import htsjdk.variant.vcf.VCFHeaderLine;
-import htsjdk.variant.vcf.VCFHeaderLineCount;
-import htsjdk.variant.vcf.VCFHeaderLineType;
-import htsjdk.variant.vcf.VCFInfoHeaderLine;
-
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.Dna;
 import jalview.api.AlignViewControllerGuiI;
+import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.GeneLociI;
@@ -35,6 +27,17 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import htsjdk.samtools.util.CloseableIterator;
+import htsjdk.variant.variantcontext.Allele;
+import htsjdk.variant.variantcontext.VariantContext;
+import htsjdk.variant.vcf.VCFHeader;
+import htsjdk.variant.vcf.VCFHeaderLine;
+import htsjdk.variant.vcf.VCFHeaderLineCount;
+import htsjdk.variant.vcf.VCFHeaderLineType;
+import htsjdk.variant.vcf.VCFInfoHeaderLine;
 
 /**
  * A class to read VCF data (using the htsjdk) and add variants as sequence
@@ -45,6 +48,18 @@ import java.util.Map.Entry;
 public class VCFLoader
 {
   /*
+   * Lookup keys, and default values, for Preference entries that describe
+   * patterns for VCF and VEP fields to capture 
+   */
+  private static final String VEP_FIELDS_PREF = "VEP_FIELDS";
+
+  private static final String VCF_FIELDS_PREF = "VCF_FIELDS";
+
+  private static final String DEFAULT_VCF_FIELDS = "AF,AC*";
+
+  private static final String DEFAULT_VEP_FIELDS = ".*";// "Allele,Consequence,IMPACT,SWISSPROT,SIFT,PolyPhen,CLIN_SIG";
+
+  /*
    * keys to fields of VEP CSQ consequence data
    * see https://www.ensembl.org/info/docs/tools/vep/vep_formats.html
    */
@@ -54,23 +69,16 @@ public class VCFLoader
   private static final String FEATURE_KEY = "Feature"; // Ensembl stable id
 
   /*
-   * what comes before column headings in CSQ Description field
-   */
-  private static final String FORMAT = "Format: ";
-
-  /*
    * default VCF INFO key for VEP consequence data
    * NB this can be overridden running VEP with --vcf_info_field
-   * - we don't handle this case (require CSQ identifier)
+   * - we don't handle this case (require identifier to be CSQ)
    */
-  private static final String CSQ = "CSQ";
+  private static final String CSQ_FIELD = "CSQ";
 
   /*
-   * separator for fields in consequence data
+   * separator for fields in consequence data is '|'
    */
-  private static final String PIPE = "|";
-
-  private static final String PIPE_REGEX = "\\" + PIPE;
+  private static final String PIPE_REGEX = "\\|";
 
   /*
    * key for Allele Frequency output by VEP
@@ -126,6 +134,19 @@ public class VCFLoader
    */
   private String sourceId;
 
+  /*
+   * The INFO IDs of data that is both present in the VCF file, and
+   * also matched by any filters for data of interest
+   */
+  List<String> vcfFieldsOfInterest;
+
+  /*
+   * The field offsets and identifiers for VEP (CSQ) data that is both present
+   * in the VCF file, and also matched by any filters for data of interest
+   * for example 0 -> Allele, 1 -> Consequence, ..., 36 -> SIFT, ...
+   */
+  Map<Integer, String> vepFieldsOfInterest;
+
   /**
    * Constructor given an alignment context
    * 
@@ -136,7 +157,7 @@ public class VCFLoader
     al = alignment;
 
     // map of species!chromosome!fromAssembly!toAssembly to {fromRange, toRange}
-    assemblyMappings = new HashMap<String, Map<int[], int[]>>();
+    assemblyMappings = new HashMap<>();
   }
 
   /**
@@ -193,7 +214,7 @@ public class VCFLoader
       /*
        * get offset of CSQ ALLELE_NUM and Feature if declared
        */
-      locateCsqFields();
+      parseCsqHeader();
 
       VCFHeaderLine ref = header
               .getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
@@ -253,11 +274,15 @@ public class VCFLoader
    * Reads metadata (such as INFO field descriptions and datatypes) and saves
    * them for future reference
    * 
-   * @param sourceId
+   * @param theSourceId
    */
-  void saveMetadata(String sourceId)
+  void saveMetadata(String theSourceId)
   {
-    FeatureSource metadata = new FeatureSource(sourceId);
+    List<Pattern> vcfFieldPatterns = getFieldMatchers(VCF_FIELDS_PREF,
+            DEFAULT_VCF_FIELDS);
+    vcfFieldsOfInterest = new ArrayList<>();
+
+    FeatureSource metadata = new FeatureSource(theSourceId);
 
     for (VCFInfoHeaderLine info : header.getInfoHeaderLines())
     {
@@ -285,34 +310,65 @@ public class VCFLoader
       }
       metadata.setAttributeName(attributeId, desc);
       metadata.setAttributeType(attributeId, attType);
+
+      if (isFieldWanted(attributeId, vcfFieldPatterns))
+      {
+        vcfFieldsOfInterest.add(attributeId);
+      }
     }
 
-    FeatureSources.getInstance().addSource(sourceId, metadata);
+    FeatureSources.getInstance().addSource(theSourceId, metadata);
   }
 
   /**
-   * Records the position of selected fields defined in the CSQ INFO header (if
-   * there is one). CSQ fields are declared in the CSQ INFO Description e.g.
+   * Answers true if the field id is matched by any of the filter patterns, else
+   * false. Matching is against regular expression patterns, and is not
+   * case-sensitive.
+   * 
+   * @param id
+   * @param filters
+   * @return
+   */
+  private boolean isFieldWanted(String id, List<Pattern> filters)
+  {
+    for (Pattern p : filters)
+    {
+      if (p.matcher(id.toUpperCase()).matches())
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Records 'wanted' fields defined in the CSQ INFO header (if there is one).
+   * Also records the position of selected fields (Allele, ALLELE_NUM, Feature)
+   * required for processing.
+   * <p>
+   * CSQ fields are declared in the CSQ INFO Description e.g.
    * <p>
    * Description="Consequence ...from ... VEP. Format: Allele|Consequence|...
    */
-  protected void locateCsqFields()
+  protected void parseCsqHeader()
   {
-    VCFInfoHeaderLine csqInfo = header.getInfoHeaderLine(CSQ);
+    List<Pattern> vepFieldFilters = getFieldMatchers(VEP_FIELDS_PREF,
+            DEFAULT_VEP_FIELDS);
+    vepFieldsOfInterest = new HashMap<>();
+
+    VCFInfoHeaderLine csqInfo = header.getInfoHeaderLine(CSQ_FIELD);
     if (csqInfo == null)
     {
       return;
     }
 
+    /*
+     * parse out the pipe-separated list of CSQ fields; we assume here that
+     * these form the last part of the description, and contain no spaces
+     */
     String desc = csqInfo.getDescription();
-    int formatPos = desc.indexOf(FORMAT);
-    if (formatPos == -1)
-    {
-      System.err.println("Parse error, failed to find " + FORMAT
-              + " in " + desc);
-      return;
-    }
-    desc = desc.substring(formatPos + FORMAT.length());
+    int spacePos = desc.lastIndexOf(" ");
+    desc = desc.substring(spacePos + 1);
 
     if (desc != null)
     {
@@ -332,12 +388,51 @@ public class VCFLoader
         {
           csqFeatureFieldIndex = index;
         }
+
+        if (isFieldWanted(field, vepFieldFilters))
+        {
+          vepFieldsOfInterest.put(index, field);
+        }
+
         index++;
       }
     }
   }
 
   /**
+   * Reads the Preference value for the given key, with default specified if no
+   * preference set. The value is interpreted as a comma-separated list of
+   * regular expressions, and converted into a list of compiled patterns ready
+   * for matching. Patterns are forced to upper-case for non-case-sensitive
+   * matching.
+   * <p>
+   * This supports user-defined filters for fields of interest to capture while
+   * processing data. For example, VCF_FIELDS = AF,AC* would mean that VCF INFO
+   * fields with an ID of AF, or starting with AC, would be matched.
+   * 
+   * @param key
+   * @param def
+   * @return
+   */
+  private List<Pattern> getFieldMatchers(String key, String def)
+  {
+    String pref = Cache.getDefault(key, def);
+    List<Pattern> patterns = new ArrayList<>();
+    String[] tokens = pref.split(",");
+    for (String token : tokens)
+    {
+      try
+      {
+      patterns.add(Pattern.compile(token.toUpperCase()));
+      } catch (PatternSyntaxException e)
+      {
+        System.err.println("Invalid pattern ignored: " + token);
+      }
+    }
+    return patterns;
+  }
+
+  /**
    * Transfers VCF features to sequences to which this sequence has a mapping.
    * If the mapping is 3:1, computes peptide variants from nucleotide variants.
    * 
@@ -702,13 +797,21 @@ public class VCFLoader
        * extract Consequence data (if present) that we are able to
        * associated with the allele for this variant feature
        */
-      if (CSQ.equals(key))
+      if (CSQ_FIELD.equals(key))
       {
         addConsequences(variant, seq, sf, altAlelleIndex);
         continue;
       }
 
       /*
+       * filter out fields we don't want to capture
+       */
+      if (!vcfFieldsOfInterest.contains(key))
+      {
+        continue;
+      }
+
+      /*
        * we extract values for other data which are allele-specific; 
        * these may be per alternate allele (INFO[key].Number = 'A') 
        * or per allele including reference (INFO[key].Number = 'R') 
@@ -775,7 +878,7 @@ public class VCFLoader
   protected void addConsequences(VariantContext variant, SequenceI seq,
           SequenceFeature sf, int altAlelleIndex)
   {
-    Object value = variant.getAttribute(CSQ);
+    Object value = variant.getAttribute(CSQ_FIELD);
 
     if (value == null || !(value instanceof ArrayList<?>))
     {
@@ -808,8 +911,11 @@ public class VCFLoader
       }
     }
 
-    StringBuilder sb = new StringBuilder(128);
-    boolean found = false;
+    /*
+     * inspect CSQ consequences; where possible restrict to the consequence
+     * associated with the current transcript (Feature)
+     */
+    Map<String, String> csqValues = new HashMap<>();
 
     for (String consequence : consequences)
     {
@@ -818,18 +924,29 @@ public class VCFLoader
       if (includeConsequence(csqFields, matchFeature, variant,
               altAlelleIndex))
       {
-        if (found)
+        /*
+         * inspect individual fields of this consequence, copying non-null
+         * values which are 'fields of interest'
+         */
+        int i = 0;
+        for (String field : csqFields)
         {
-          sb.append(COMMA);
+          if (field != null && field.length() > 0)
+          {
+            String id = vepFieldsOfInterest.get(i);
+            if (id != null)
+            {
+              csqValues.put(id, field);
+            }
+          }
+          i++;
         }
-        found = true;
-        sb.append(consequence);
       }
     }
 
-    if (found)
+    if (!csqValues.isEmpty())
     {
-      sf.setValue(CSQ, sb.toString());
+      sf.setValue(CSQ_FIELD, csqValues);
     }
   }
 
index 168ab54..71a89b0 100644 (file)
@@ -78,10 +78,10 @@ public class FeatureColour implements FeatureColourI
   private boolean colourByLabel;
 
   /*
-   * if not null, the value of this named attribute is used for
-   * colourByLabel or graduatedColour
+   * if not null, the value of [attribute, [sub-attribute] ...]
+   *  is used for colourByLabel or graduatedColour
    */
-  private String byAttributeName;
+  private String[] attributeName;
 
   private float threshold;
 
@@ -371,7 +371,7 @@ public class FeatureColour implements FeatureColourI
     base = fc.base;
     range = fc.range;
     isHighToLow = fc.isHighToLow;
-    byAttributeName = fc.byAttributeName;
+    attributeName = fc.attributeName;
     setAboveThreshold(fc.isAboveThreshold());
     setBelowThreshold(fc.isBelowThreshold());
     setThreshold(fc.getThreshold());
@@ -593,8 +593,8 @@ public class FeatureColour implements FeatureColourI
   {
     if (isColourByLabel())
     {
-      String label = byAttributeName == null ? feature.getDescription()
-              : feature.getValueAsString(byAttributeName);
+      String label = attributeName == null ? feature.getDescription()
+              : feature.getValueAsString(attributeName);
       return label == null ? noColour : ColorUtils
               .createColourFromName(label);
     }
@@ -611,11 +611,11 @@ public class FeatureColour implements FeatureColourI
      * no such attribute is assigned the 'no value' colour
      */
     float scr = feature.getScore();
-    if (byAttributeName != null)
+    if (attributeName != null)
     {
       try
       {
-        String attVal = feature.getValueAsString(byAttributeName);
+        String attVal = feature.getValueAsString(attributeName);
         scr = Float.valueOf(attVal);
       } catch (Throwable e)
       {
@@ -746,19 +746,19 @@ public class FeatureColour implements FeatureColourI
   @Override
   public boolean isColourByAttribute()
   {
-    return byAttributeName != null;
+    return attributeName != null;
   }
 
   @Override
-  public String getAttributeName()
+  public String[] getAttributeName()
   {
-    return byAttributeName;
+    return attributeName;
   }
 
   @Override
-  public void setAttributeName(String name)
+  public void setAttributeName(String... name)
   {
-    byAttributeName = name;
+    attributeName = name;
   }
 
 }
index cd952e7..ef1c702 100644 (file)
@@ -19,18 +19,20 @@ import java.util.function.Function;
  */
 public class KeyedMatcher implements KeyedMatcherI
 {
-  final private String key;
+  private static final String COLON = ":";
+
+  final private String[] key;
 
   final private MatcherI matcher;
 
   /**
    * Constructor given a key, a test condition and a match pattern
    * 
-   * @param theKey
    * @param cond
    * @param pattern
+   * @param theKey
    */
-  public KeyedMatcher(String theKey, Condition cond, String pattern)
+  public KeyedMatcher(Condition cond, String pattern, String... theKey)
   {
     key = theKey;
     matcher = new Matcher(cond, pattern);
@@ -41,25 +43,25 @@ public class KeyedMatcher implements KeyedMatcherI
    * to. Note that if a non-numerical condition is specified, the float will be
    * converted to a string.
    * 
-   * @param theKey
    * @param cond
    * @param value
+   * @param theKey
    */
-  public KeyedMatcher(String theKey, Condition cond, float value)
+  public KeyedMatcher(Condition cond, float value, String... theKey)
   {
     key = theKey;
     matcher = new Matcher(cond, value);
   }
 
   @Override
-  public boolean matches(Function<String, String> valueProvider)
+  public boolean matches(Function<String[], String> valueProvider)
   {
     String value = valueProvider.apply(key);
     return matcher.matches(value);
   }
 
   @Override
-  public String getKey()
+  public String[] getKey()
   {
     return key;
   }
@@ -78,8 +80,8 @@ public class KeyedMatcher implements KeyedMatcherI
   public String toString()
   {
     StringBuilder sb = new StringBuilder();
-    sb.append(key).append(" ").append(matcher.getCondition().toString())
-            .append(" ");
+    sb.append(String.join(COLON, key)).append(" ")
+            .append(matcher.getCondition().toString()).append(" ");
     if (matcher.getCondition().isNumeric())
     {
       sb.append(matcher.getPattern());
index e9fe014..e8d71c1 100644 (file)
@@ -18,14 +18,14 @@ public interface KeyedMatcherI
    * @param valueProvider
    * @return
    */
-  boolean matches(Function<String, String> valueProvider);
+  boolean matches(Function<String[], String> valueProvider);
 
   /**
    * Answers the value key this matcher operates on
    * 
    * @return
    */
-  String getKey();
+  String[] getKey();
 
   /**
    * Answers the match condition that is applied
index 35a41c2..a4be48a 100644 (file)
@@ -19,7 +19,7 @@ public class KeyedMatcherSet implements KeyedMatcherSetI
   }
 
   @Override
-  public boolean matches(Function<String, String> valueProvider)
+  public boolean matches(Function<String[], String> valueProvider)
   {
     /*
      * no conditions matches anything
index 25dc96e..3e9f5b6 100644 (file)
@@ -18,7 +18,7 @@ public interface KeyedMatcherSetI
    * @param valueProvider
    * @return
    */
-  boolean matches(Function<String, String> valueProvider);
+  boolean matches(Function<String[], String> valueProvider);
 
   /**
    * Answers a new object that matches the logical AND of this and m
index a213a17..715694c 100644 (file)
@@ -37,8 +37,8 @@ public class Matcher implements MatcherI
    * @param compareTo
    * @return
    * @throws NumberFormatException
-   *           if a numerical condition is specified with a non-numeric
-   *           comparision value
+   *           if a numerical condition is specified with a non-numeric comparison
+   *           value
    * @throws NullPointerException
    *           if a null condition or comparison string is specified
    */
index c2f5bb7..28fceec 100644 (file)
@@ -169,7 +169,7 @@ public abstract class FeatureRendererModel
     {
       av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
     }
-    List<String> nft = new ArrayList<String>();
+    List<String> nft = new ArrayList<>();
     for (String featureType : featureTypes)
     {
       if (!fdi.isRegistered(featureType))
@@ -205,7 +205,7 @@ public abstract class FeatureRendererModel
     renderOrder = neworder;
   }
 
-  protected Map<String, float[][]> minmax = new Hashtable<String, float[][]>();
+  protected Map<String, float[][]> minmax = new Hashtable<>();
 
   public Map<String, float[][]> getMinMax()
   {
@@ -284,7 +284,7 @@ public abstract class FeatureRendererModel
      * include features at the position provided their feature type is 
      * displayed, and feature group is null or marked for display
      */
-    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    List<SequenceFeature> result = new ArrayList<>();
     if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
     {
       return result;
@@ -333,7 +333,7 @@ public abstract class FeatureRendererModel
     }
     FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed();
 
-    Set<String> oldfeatures = new HashSet<String>();
+    Set<String> oldfeatures = new HashSet<>();
     if (renderOrder != null)
     {
       for (int i = 0; i < renderOrder.length; i++)
@@ -346,7 +346,7 @@ public abstract class FeatureRendererModel
     }
 
     AlignmentI alignment = av.getAlignment();
-    List<String> allfeatures = new ArrayList<String>();
+    List<String> allfeatures = new ArrayList<>();
 
     for (int i = 0; i < alignment.getHeight(); i++)
     {
@@ -426,7 +426,7 @@ public abstract class FeatureRendererModel
      */
     if (minmax == null)
     {
-      minmax = new Hashtable<String, float[][]>();
+      minmax = new Hashtable<>();
     }
     synchronized (minmax)
     {
@@ -463,7 +463,7 @@ public abstract class FeatureRendererModel
    */
   private void updateRenderOrder(List<String> allFeatures)
   {
-    List<String> allfeatures = new ArrayList<String>(allFeatures);
+    List<String> allfeatures = new ArrayList<>(allFeatures);
     String[] oldRender = renderOrder;
     renderOrder = new String[allfeatures.size()];
     boolean initOrders = (featureOrder == null);
@@ -624,7 +624,7 @@ public abstract class FeatureRendererModel
   {
     if (featureOrder == null)
     {
-      featureOrder = new Hashtable<String, Float>();
+      featureOrder = new Hashtable<>();
     }
     featureOrder.put(type, new Float(position));
     return position;
@@ -683,7 +683,7 @@ public abstract class FeatureRendererModel
      * note visible feature ordering and colours before update
      */
     List<String> visibleFeatures = getDisplayedFeatureTypes();
-    Map<String, FeatureColourI> visibleColours = new HashMap<String, FeatureColourI>(
+    Map<String, FeatureColourI> visibleColours = new HashMap<>(
             getFeatureColours());
 
     FeaturesDisplayedI av_featuresdisplayed = null;
@@ -843,7 +843,7 @@ public abstract class FeatureRendererModel
   {
     if (featureGroups != null)
     {
-      List<String> gp = new ArrayList<String>();
+      List<String> gp = new ArrayList<>();
 
       for (String grp : featureGroups.keySet())
       {
@@ -889,7 +889,7 @@ public abstract class FeatureRendererModel
   @Override
   public Map<String, FeatureColourI> getDisplayedFeatureCols()
   {
-    Map<String, FeatureColourI> fcols = new Hashtable<String, FeatureColourI>();
+    Map<String, FeatureColourI> fcols = new Hashtable<>();
     if (getViewport().getFeaturesDisplayed() == null)
     {
       return fcols;
@@ -917,7 +917,7 @@ public abstract class FeatureRendererModel
   public List<String> getDisplayedFeatureTypes()
   {
     List<String> typ = getRenderOrder();
-    List<String> displayed = new ArrayList<String>();
+    List<String> displayed = new ArrayList<>();
     FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed();
     if (feature_disp != null)
     {
@@ -938,7 +938,7 @@ public abstract class FeatureRendererModel
   @Override
   public List<String> getDisplayedFeatureGroups()
   {
-    List<String> _gps = new ArrayList<String>();
+    List<String> _gps = new ArrayList<>();
     for (String gp : getFeatureGroups())
     {
       if (checkGroupVisibility(gp, false))
@@ -973,7 +973,7 @@ public abstract class FeatureRendererModel
   public List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence,
           int resNo)
   {
-    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    List<SequenceFeature> result = new ArrayList<>();
     if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
     {
       return result;
diff --git a/test/jalview/datamodel/features/FeatureAttributesTest.java b/test/jalview/datamodel/features/FeatureAttributesTest.java
new file mode 100644 (file)
index 0000000..e464326
--- /dev/null
@@ -0,0 +1,41 @@
+package jalview.datamodel.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Comparator;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.Test;
+
+public class FeatureAttributesTest
+{
+
+  /**
+   * Test the method that keeps attribute names in non-case-sensitive order,
+   * including handling of 'compound' names
+   */
+  @Test(groups="Functional")
+  public void testAttributeNameComparator()
+  {
+    FeatureAttributes fa = FeatureAttributes.getInstance();
+    Comparator<String[]> comp = (Comparator<String[]>) PA.getValue(fa,
+            "comparator");
+
+    assertEquals(
+            comp.compare(new String[] { "CSQ" }, new String[] { "csq" }), 0);
+
+    assertTrue(comp.compare(new String[] { "CSQ", "a" },
+            new String[] { "csq" }) > 0);
+
+    assertTrue(comp.compare(new String[] { "CSQ" }, new String[] { "csq",
+        "b" }) < 0);
+
+    assertTrue(comp.compare(new String[] { "CSQ", "AF" }, new String[] {
+        "csq", "ac" }) > 0);
+
+    assertTrue(comp.compare(new String[] { "CSQ", "ac" }, new String[] {
+        "csq", "AF" }) < 0);
+  }
+}
index 812fd8f..5ed0cac 100644 (file)
@@ -96,57 +96,6 @@ public class AlignViewportTest
     testee = new AlignViewport(al);
   }
 
-  @Test(groups = { "Functional" })
-  public void testCollateForPdb()
-  {
-    // JBP: What behaviour is this supposed to test ?
-    /*
-     * Set up sequence pdb ids
-     */
-    PDBEntry pdb1 = new PDBEntry("1ABC", "B", Type.PDB, "1ABC.pdb");
-    PDBEntry pdb2 = new PDBEntry("2ABC", "C", Type.PDB, "2ABC.pdb");
-    PDBEntry pdb3 = new PDBEntry("3ABC", "D", Type.PDB, "3ABC.pdb");
-
-    /*
-     * seq1 and seq3 refer to 1abcB, seq2 to 2abcC, none to 3abcD
-     */
-    al.getSequenceAt(0).getDatasetSequence()
-            .addPDBId(new PDBEntry("1ABC", "B", Type.PDB, "1ABC.pdb"));
-    al.getSequenceAt(2).getDatasetSequence()
-            .addPDBId(new PDBEntry("1ABC", "B", Type.PDB, "1ABC.pdb"));
-    al.getSequenceAt(1).getDatasetSequence()
-            .addPDBId(new PDBEntry("2ABC", "C", Type.PDB, "2ABC.pdb"));
-    /*
-     * Add a second chain PDB xref to Seq2 - should not result in a duplicate in
-     * the results
-     */
-    al.getSequenceAt(1).getDatasetSequence()
-            .addPDBId(new PDBEntry("2ABC", "D", Type.PDB, "2ABC.pdb"));
-    /*
-     * Seq3 refers to 3abc - this does not match 3ABC (as the code stands)
-     */
-    al.getSequenceAt(2).getDatasetSequence()
-            .addPDBId(new PDBEntry("3abc", "D", Type.PDB, "3ABC.pdb"));
-
-    /*
-     * run method under test
-     */
-    SequenceI[][] seqs = testee.collateForPDB(new PDBEntry[] { pdb1, pdb2,
-        pdb3 });
-
-    // seq1 and seq3 refer to PDBEntry[0]
-    assertEquals(2, seqs[0].length);
-    assertSame(al.getSequenceAt(0), seqs[0][0]);
-    assertSame(al.getSequenceAt(2), seqs[0][1]);
-
-    // seq2 refers to PDBEntry[1]
-    assertEquals(1, seqs[1].length);
-    assertSame(al.getSequenceAt(1), seqs[1][0]);
-
-    // no sequence refers to PDBEntry[2]
-    assertEquals(0, seqs[2].length);
-  }
-
   /**
    * Test that a mapping is not deregistered when a second view is closed but
    * the first still holds a reference to the mapping
index c1c1d5c..4d5b114 100644 (file)
@@ -1,10 +1,17 @@
 package jalview.gui;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
 
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.PDBEntry.Type;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+
+import java.util.Map;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -20,9 +27,11 @@ public class StructureViewerTest
   }
 
   @Test(groups = "Functional")
-  public void testGetUniquePdbFiles()
+  public void testGetSequencesForPdbs()
   {
-    assertNull(StructureViewer.getUniquePdbFiles(null));
+    StructureViewer sv = new StructureViewer(null);
+
+    assertNull(sv.getSequencesForPdbs(null, null));
 
     PDBEntry pdbe1 = new PDBEntry("1A70", "A", Type.PDB, "path1");
     PDBEntry pdbe2 = new PDBEntry("3A6S", "A", Type.PDB, "path2");
@@ -30,13 +39,45 @@ public class StructureViewerTest
     PDBEntry pdbe4 = new PDBEntry("1GAQ", "A", Type.PDB, null);
     PDBEntry pdbe5 = new PDBEntry("3A6S", "B", Type.PDB, "path2");
     PDBEntry pdbe6 = new PDBEntry("1GAQ", "B", Type.PDB, null);
+    PDBEntry[] pdbs = new PDBEntry[] { pdbe1, pdbe2, pdbe3, pdbe4, pdbe5,
+        pdbe6 };
+
+    /*
+     * seq1 ... seq6 associated with pdbe1 ... pdbe6
+     */
+    SequenceI[] seqs = new SequenceI[pdbs.length];
+    for (int i = 0; i < seqs.length; i++)
+    {
+      seqs[i] = new Sequence("Seq" + i, "abc");
+    }
 
     /*
-     * pdbe2 and pdbe5 get removed as having a duplicate file path
+     * pdbe3/5/6 should get removed as having a duplicate file path
      */
-    PDBEntry[] uniques = StructureViewer.getUniquePdbFiles(new PDBEntry[] {
-        pdbe1, pdbe2, pdbe3, pdbe4, pdbe5, pdbe6 });
-    assertEquals(uniques,
- new PDBEntry[] { pdbe1, pdbe2, pdbe4, pdbe6 });
+    Map<PDBEntry, SequenceI[]> uniques = sv.getSequencesForPdbs(pdbs, seqs);
+    assertTrue(uniques.containsKey(pdbe1));
+    assertTrue(uniques.containsKey(pdbe2));
+    assertFalse(uniques.containsKey(pdbe3));
+    assertTrue(uniques.containsKey(pdbe4));
+    assertFalse(uniques.containsKey(pdbe5));
+    assertFalse(uniques.containsKey(pdbe6));
+
+    // 1A70 associates with seq1 and seq3
+    SequenceI[] ss = uniques.get(pdbe1);
+    assertEquals(ss.length, 2);
+    assertSame(seqs[0], ss[0]);
+    assertSame(seqs[2], ss[1]);
+
+    // 3A6S has seq2 and seq5
+    ss = uniques.get(pdbe2);
+    assertEquals(ss.length, 2);
+    assertSame(seqs[1], ss[0]);
+    assertSame(seqs[4], ss[1]);
+
+    // 1GAQ has seq4 and seq6
+    ss = uniques.get(pdbe4);
+    assertEquals(ss.length, 2);
+    assertSame(seqs[3], ss[0]);
+    assertSame(seqs[5], ss[1]);
   }
 }
index 0b5b6bd..030a90f 100644 (file)
@@ -20,7 +20,7 @@ public class Blosum62ColourSchemeTest
    * </ul>
    * <ul>
    */
-  @Test
+  @Test(groups = "Functional")
   public void testFindColour()
   {
     ColourSchemeI blosum = new Blosum62ColourScheme();
index d2db258..3fc6fe0 100644 (file)
@@ -818,7 +818,7 @@ public class MapListTest
   /**
    * Test the method that compounds ('traverses') two mappings
    */
-  @Test
+  @Test(groups = "Functional")
   public void testTraverse()
   {
     /*
index fc84741..dc23472 100644 (file)
@@ -6,7 +6,7 @@ import org.testng.annotations.Test;
 
 public class MathUtilsTest
 {
-  @Test
+  @Test(groups = "Functional")
   public void testGcd()
   {
     assertEquals(MathUtils.gcd(0, 0), 0);
index 270aa2a..9d8b225 100644 (file)
@@ -8,7 +8,7 @@ import org.testng.annotations.Test;
 
 public class ConditionTest
 {
-  @Test
+  @Test(groups = "Functional")
   public void testToString()
   {
     Locale.setDefault(Locale.UK);
index 3018cb6..3d597d2 100644 (file)
@@ -12,13 +12,13 @@ import org.testng.annotations.Test;
 
 public class KeyedMatcherSetTest
 {
-  @Test
+  @Test(groups = "Functional")
   public void testMatches()
   {
     /*
      * a numeric matcher - MatcherTest covers more conditions
      */
-    KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+    KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF");
     KeyedMatcherSetI kms = new KeyedMatcherSet();
     kms.and(km);
     assertTrue(kms.matches(key -> "-2"));
@@ -31,24 +31,25 @@ public class KeyedMatcherSetTest
     /*
      * a string pattern matcher
      */
-    km = new KeyedMatcher("AF", Condition.Contains, "Cat");
+    km = new KeyedMatcher(Condition.Contains, "Cat", "AF");
     kms = new KeyedMatcherSet();
     kms.and(km);
     assertTrue(kms
-            .matches(key -> "AF".equals(key) ? "raining cats and dogs"
+            .matches(key -> "AF".equals(key[0]) ? "raining cats and dogs"
             : "showers"));
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testAnd()
   {
     // condition1: AF value contains "dog" (matches)
-    KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog");
+    KeyedMatcherI km1 = new KeyedMatcher(Condition.Contains, "dog", "AF");
     // condition 2: CSQ value does not contain "how" (does not match)
-    KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains,
-            "how");
+    KeyedMatcherI km2 = new KeyedMatcher(Condition.NotContains, "how",
+            "CSQ");
 
-    Function<String, String> vp = key -> "AF".equals(key) ? "raining cats and dogs"
+    Function<String[], String> vp = key -> "AF".equals(key[0])
+            ? "raining cats and dogs"
             : "showers";
     assertTrue(km1.matches(vp));
     assertFalse(km2.matches(vp));
@@ -61,13 +62,14 @@ public class KeyedMatcherSetTest
     assertFalse(kms.matches(vp));
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testToString()
   {
-    KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.LT, 1.2f);
+    KeyedMatcherI km1 = new KeyedMatcher(Condition.LT, 1.2f, "AF");
     assertEquals(km1.toString(), "AF < 1.2");
 
-    KeyedMatcher km2 = new KeyedMatcher("CLIN_SIG", Condition.NotContains, "path");
+    KeyedMatcher km2 = new KeyedMatcher(Condition.NotContains, "path",
+            "CLIN_SIG");
     assertEquals(km2.toString(), "CLIN_SIG Does not contain 'PATH'");
 
     /*
@@ -93,16 +95,17 @@ public class KeyedMatcherSetTest
             "(AF < 1.2) OR (CLIN_SIG Does not contain 'PATH')");
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testOr()
   {
     // condition1: AF value contains "dog" (matches)
-    KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.Contains, "dog");
+    KeyedMatcherI km1 = new KeyedMatcher(Condition.Contains, "dog", "AF");
     // condition 2: CSQ value does not contain "how" (does not match)
-    KeyedMatcherI km2 = new KeyedMatcher("CSQ", Condition.NotContains,
-            "how");
+    KeyedMatcherI km2 = new KeyedMatcher(Condition.NotContains, "how",
+            "CSQ");
 
-    Function<String, String> vp = key -> "AF".equals(key) ? "raining cats and dogs"
+    Function<String[], String> vp = key -> "AF".equals(key[0])
+            ? "raining cats and dogs"
             : "showers";
     assertTrue(km1.matches(vp));
     assertFalse(km2.matches(vp));
@@ -114,17 +117,17 @@ public class KeyedMatcherSetTest
     assertTrue(kms.matches(vp));
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testIsEmpty()
   {
-    KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+    KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF");
     KeyedMatcherSetI kms = new KeyedMatcherSet();
     assertTrue(kms.isEmpty());
     kms.and(km);
     assertFalse(kms.isEmpty());
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testGetMatchers()
   {
     KeyedMatcherSetI kms = new KeyedMatcherSet();
@@ -138,7 +141,7 @@ public class KeyedMatcherSetTest
     /*
      * one matcher:
      */
-    KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.GE, -2F);
+    KeyedMatcherI km1 = new KeyedMatcher(Condition.GE, -2F, "AF");
     kms.and(km1);
     iterator = kms.getMatchers().iterator();
     assertSame(km1, iterator.next());
@@ -147,11 +150,44 @@ public class KeyedMatcherSetTest
     /*
      * two matchers:
      */
-    KeyedMatcherI km2 = new KeyedMatcher("AF", Condition.LT, 8F);
+    KeyedMatcherI km2 = new KeyedMatcher(Condition.LT, 8F, "AF");
     kms.and(km2);
     iterator = kms.getMatchers().iterator();
     assertSame(km1, iterator.next());
     assertSame(km2, iterator.next());
     assertFalse(iterator.hasNext());
   }
+
+  /**
+   * Tests for the 'compound attribute' key i.e. where first key's value is a map
+   * from which we take the value for the second key, e.g. CSQ : Consequence
+   */
+  @Test(groups = "Functional")
+  public void testMatches_compoundKey()
+  {
+    /*
+     * a numeric matcher - MatcherTest covers more conditions
+     */
+    KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "CSQ",
+            "Consequence");
+    KeyedMatcherSetI kms = new KeyedMatcherSet();
+    kms.and(km);
+    assertTrue(kms.matches(key -> "-2"));
+    assertTrue(kms.matches(key -> "-1"));
+    assertFalse(kms.matches(key -> "-3"));
+    assertFalse(kms.matches(key -> ""));
+    assertFalse(kms.matches(key -> "junk"));
+    assertFalse(kms.matches(key -> null));
+  
+    /*
+     * a string pattern matcher
+     */
+    km = new KeyedMatcher(Condition.Contains, "Cat", "CSQ", "Consequence");
+    kms = new KeyedMatcherSet();
+    kms.and(km);
+    assertTrue(kms.matches(key -> "csq".equalsIgnoreCase(key[0])
+            && "Consequence".equalsIgnoreCase(key[1])
+                    ? "raining cats and dogs"
+                    : "showers"));
+  }
 }
index 164b8eb..01d0067 100644 (file)
@@ -14,7 +14,7 @@ public class KeyedMatcherTest
     /*
      * a numeric matcher - MatcherTest covers more conditions
      */
-    KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+    KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF");
     assertTrue(km.matches(key -> "-2"));
     assertTrue(km.matches(key -> "-1"));
     assertFalse(km.matches(key -> "-3"));
@@ -25,9 +25,10 @@ public class KeyedMatcherTest
     /*
      * a string pattern matcher
      */
-    km = new KeyedMatcher("AF", Condition.Contains, "Cat");
-    assertTrue(km.matches(key -> "AF".equals(key) ? "raining cats and dogs"
-            : "showers"));
+    km = new KeyedMatcher(Condition.Contains, "Cat", "AF");
+    assertTrue(
+            km.matches(key -> "AF".equals(key[0]) ? "raining cats and dogs"
+                    : "showers"));
   }
 
   @Test
@@ -36,21 +37,27 @@ public class KeyedMatcherTest
     /*
      * toString uses the i18n translation of the enum conditions
      */
-    KeyedMatcherI km = new KeyedMatcher("AF", Condition.LT, 1.2f);
+    KeyedMatcherI km = new KeyedMatcher(Condition.LT, 1.2f, "AF");
     assertEquals(km.toString(), "AF < 1.2");
   }
 
   @Test
   public void testGetKey()
   {
-    KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
-    assertEquals(km.getKey(), "AF");
+    KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF");
+    assertEquals(km.getKey(), new String[] { "AF" });
+
+    /*
+     * compound key (attribute / subattribute)
+     */
+    km = new KeyedMatcher(Condition.GE, -2F, "CSQ", "Consequence");
+    assertEquals(km.getKey(), new String[] { "CSQ", "Consequence" });
   }
 
   @Test
   public void testGetMatcher()
   {
-    KeyedMatcherI km = new KeyedMatcher("AF", Condition.GE, -2F);
+    KeyedMatcherI km = new KeyedMatcher(Condition.GE, -2F, "AF");
     assertEquals(km.getMatcher().getCondition(), Condition.GE);
     assertEquals(km.getMatcher().getFloatValue(), -2F);
     assertEquals(km.getMatcher().getPattern(), "-2.0");
index 489cdce..ee0ff82 100644 (file)
@@ -10,7 +10,7 @@ import org.testng.annotations.Test;
 
 public class MatcherTest
 {
-  @Test
+  @Test(groups = "Functional")
   public void testConstructor()
   {
     MatcherI m = new Matcher(Condition.Contains, "foo");
@@ -55,7 +55,7 @@ public class MatcherTest
   /**
    * Tests for float comparison conditions
    */
-  @Test
+  @Test(groups = "Functional")
   public void testMatches_float()
   {
     /*
@@ -107,7 +107,7 @@ public class MatcherTest
     assertTrue(m.matches("1.9"));
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testMatches_floatNullOrInvalid()
   {
     for (Condition cond : Condition.values())
@@ -125,7 +125,7 @@ public class MatcherTest
   /**
    * Tests for string comparison conditions
    */
-  @Test
+  @Test(groups = "Functional")
   public void testMatches_pattern()
   {
     /*
@@ -177,7 +177,7 @@ public class MatcherTest
   /**
    * If a float is passed with a string condition it gets converted to a string
    */
-  @Test
+  @Test(groups = "Functional")
   public void testMatches_floatWithStringCondition()
   {
     MatcherI m = new Matcher(Condition.Contains, 1.2e-6f);
@@ -189,7 +189,7 @@ public class MatcherTest
     assertFalse(m.matches("0.0000001f"));
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testToString()
   {
     MatcherI m = new Matcher(Condition.LT, 1.2e-6f);
@@ -202,7 +202,7 @@ public class MatcherTest
     assertEquals(m.toString(), "Contains '-1.2'");
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testEquals()
   {
     /*
@@ -235,7 +235,7 @@ public class MatcherTest
     assertFalse(m.equals(new Matcher(Condition.LT, -1.1f)));
   }
 
-  @Test
+  @Test(groups = "Functional")
   public void testHashCode()
   {
     MatcherI m1 = new Matcher(Condition.NotMatches, "ABC");