JAL-3449 merge from develop
authorBen Soares <bsoares@dundee.ac.uk>
Tue, 18 Feb 2020 19:49:38 +0000 (19:49 +0000)
committerBen Soares <bsoares@dundee.ac.uk>
Tue, 18 Feb 2020 19:49:38 +0000 (19:49 +0000)
30 files changed:
AUTHORS
RELEASE
build.gradle
examples/testdata/CantShowEnsemblCrossrefsTwice.jvp [new file with mode: 0644]
help/help/help.jhm
help/help/helpTOC.xml
help/help/html/features/featuresFormat.html
help/help/html/features/importvcf.html
help/help/html/releases.html
help/help/html/whatsNew.html
src/jalview/analysis/AlignmentUtils.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/ext/ensembl/EnsemblSeqProxy.java
src/jalview/io/FeaturesFile.java
src/jalview/io/gff/Gff2Helper.java
src/jalview/io/gff/Gff3Helper.java
src/jalview/io/gff/GffHelperBase.java
src/jalview/io/gff/GffHelperI.java
src/jalview/io/vcf/VCFLoader.java
src/jalview/project/Jalview2XML.java
src/jalview/util/StringUtils.java
src/jalview/ws/dbsources/EmblXmlSource.java
test/jalview/ext/ensembl/EnsemblSeqProxyTest.java
test/jalview/gui/SeqPanelTest.java
test/jalview/io/CrossRef2xmlTests.java
test/jalview/io/FeaturesFileTest.java
test/jalview/io/gff/GffHelperBaseTest.java
test/jalview/io/vcf/VCFLoaderTest.java
test/jalview/io/vcf/testVcf.vcf
test/jalview/util/StringUtilsTest.java

diff --git a/AUTHORS b/AUTHORS
index 2fe3fce..68142b2 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -7,7 +7,7 @@ or might otherwise be considered author of Jalview.
 The people listed below are 'The Jalview Authors', who collectively
 own the copyright to the Jalview source code and permit it to be released under GPL.
 
-This is the authoritative list: it was correct on 5th September 2018 (or the last commit date!)
+This is the authoritative list: it was correct on the last commit date!
 
 If you are releasing a version of Jalview, please make sure any
 statement of authorship in the GUI reflects the list shown here.
diff --git a/RELEASE b/RELEASE
index f6e3b96..5a89907 100644 (file)
--- a/RELEASE
+++ b/RELEASE
@@ -1,2 +1,2 @@
 jalview.release=releases/Release_2_11_1_Branch
-jalview.version=2.11.1
+jalview.version=2.11.1.0
index 05fddc0..bd31eaa 100644 (file)
@@ -9,7 +9,7 @@ import groovy.xml.XmlUtil
 
 buildscript {
   dependencies {
-    classpath 'org.openclover:clover:4.3.1'
+    classpath 'org.openclover:clover:4.4.1'
   }
 }
 
@@ -384,7 +384,7 @@ sourceSets {
     compileClasspath = files( sourceSets.test.java.outputDir )
 
     if (use_clover) {
-      compileClasspath += sourceSets.clover.compileClasspath
+      compileClasspath = sourceSets.clover.compileClasspath
     } else {
       compileClasspath += files(sourceSets.main.java.outputDir)
     }
@@ -401,8 +401,8 @@ sourceSets {
 // clover bits
 dependencies {
   if (use_clover) {
-    cloverCompile 'org.openclover:clover:4.3.1'
-    testCompile 'org.openclover:clover:4.3.1'
+    cloverCompile 'org.openclover:clover:4.4.1'
+    testCompile 'org.openclover:clover:4.4.1'
   }
 }
 
@@ -548,7 +548,7 @@ eclipse {
 
 task cloverInstr() {
   // only instrument source, we build test classes as normal
-  inputs.files files (sourceSets.main.allJava) // , fileTree(dir: testSourceDir, include: ["**/*.java"]))
+  inputs.files files (sourceSets.main.allJava,sourceSets.test.allJava) // , fileTree(dir:"$jalviewDir/$testSourceDir", include: ["**/*.java"]))
   outputs.dir cloverInstrDir
 
   doFirst {
diff --git a/examples/testdata/CantShowEnsemblCrossrefsTwice.jvp b/examples/testdata/CantShowEnsemblCrossrefsTwice.jvp
new file mode 100644 (file)
index 0000000..5e32d55
Binary files /dev/null and b/examples/testdata/CantShowEnsemblCrossrefsTwice.jvp differ
index 47f7d53..6b0a1c0 100755 (executable)
    <mapID target="overviewprefs" url="html/features/preferences.html#overview" />
    
    <mapID target="importvcf" url="html/features/importvcf.html" />
+   <mapID target="importvcf.attribs" url="html/features/importvcf.html#attribs" />
 </map>
index 7e358d6..5374e1a 100755 (executable)
@@ -24,7 +24,7 @@
        <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="VCF Import" target="importvcf"/>
+                               <tocitem text="VCF Variant Attributes" target="importvcf.attribs"/>
                                <tocitem text="Feature Filters and Attribute Colourschemes" target="features.featureschemes" />
                                
                </tocitem>
index 0226175..4df0b0c 100755 (executable)
     GFF data (<em>this mixed format capability was added in Jalview
       2.6</em>).
   </p>
-
+  <p>Feature attributes can be included as <code>name=value</code> pairs in GFF3 column 9, including <em>(since Jalview 2.11.1.0)</em> 'nested' sub-attributes, for example:
+  <br><code>alleles=G,A,C;AF=6;CSQ=SIFT=deleterious,tolerated,PolyPhen=possibly_damaging(0.907)</code>
+  <br>where <code>SIFT</code> and <code>PolyPhen</code> are sub-attributes of <code>CSQ</code>. This data is preserved if features are exported in GFF format (but not, currently,
+  in Jalview format).
+  </p>
   <p>
     <em>Jalview's sequence feature format</em>
   </p>
index e013472..511ff2e 100755 (executable)
     retrieved from Ensembl.
   </p>
   <p>
+    <strong><a name="attribs">Standard Variant Attributes</a></strong>
+  </p>
+  <p>Jalview decorates variant features imported from VCF files with
+    attributes that can be used to filter or shade variant annotation
+    including the following:
+  </p>
+  <ul>
+    <li><em>POS</em> - Chromosomal position as recorded in VCF</li>
+    <li><em>ID</em> - in GNOMAD releases specifies rs identifier of
+      a known dbSNP variant.</li>
+    <li>QUAL is the 'phred-scaled quality score' for the ALT
+      assertion (or quality of SNP call if there are no alternate
+      alleles). Higher is more confident.</li>
+    <li><em>FILTER</em> is 'PASS' if all filters have been passed,
+      else a list of failed filters for the variant (e.g. poor quality,
+      or insufficient sample size).</li>
+  </ul>
+  <p><em>Standard attributes were introduced in Jalview 2.11.1.0.</em> VCF field semantics are highly dependent on the source of your VCF
+    file. See <a
+      href="https://www.internationalgenome.org/wiki/Analysis/vcf4.0">https://www.internationalgenome.org/wiki/Analysis/vcf4.0</a>
+    for more information.
+  </p>
+  <p>
     <strong>Working with variants from organisms other than
       H.sapiens.</strong>
   </p>
index 0f4dd9b..bb4b386 100755 (executable)
@@ -56,34 +56,97 @@ li:before {
       <th><em>Issues Resolved</em></th>
     </tr>
     <tr>
-      <td width="60" align="center" nowrap>
-          <strong><a name="Jalview.2.11.1">2.11.1</a><br />
-            <em>16/2/2020</em></strong>
-      </td>
+      <td width="60" align="center" nowrap><strong><a
+          name="Jalview.2.11.1">2.11.1</a><a name="Jalview.2.11.1.0">.0</a><br />
+          <em>16/2/2020</em></strong></td>
       <td align="left" valign="top">
         <ul>
-          <li><!-- -->
+          <li>
+            <!-- JAL-3376 -->Record &quot;fixed column&quot; values POS, ID, QUAL, FILTER from VCF as Feature Attributes
           </li>
-        </ul>
-        <em>Deprecations</em>
+          <li>
+            <!-- JAL-3375 -->More robust VCF numeric data field validation
+            while parsing (e.g. AF* attributes)
+          </li>
+          
+          <li>
+          <!-- JAL -->
+          </li>
+        </ul> <em>Release processes</em>
+        <ul>
+          <li>
+            <!-- JAL-3508 -->New point release version scheme - 2.11.1.0
+          </li>
+        </ul> <em>Build System</em>
+        <ul>
+          <li>
+            <!-- JAL-3510 -->Clover updated to 4.4.1
+          </li>
+          <li>
+            <!-- JAL-3513 -->Test code included in Clover coverage
+            report
+          </li>
+        </ul> <em>Deprecations</em>
       </td>
       <td align="left" valign="top">
         <ul>
-        </ul>
-        <em>Java 11 Compatibility issues</em>
+          <li>
+            <!-- -->
+          </li>
+          <li>
+            <!-- JAL-3377 -->Alignment is misaligned in wrapped mode
+            with annotation and exceptions thrown when only a few
+            columns shown in wrapped mode
+          </li>
+          <li>
+            <!-- JAL-3386 -->Sequence IDs missing in headless export of
+            wrapped alignment figure with annotations
+          </li>
+          <li>
+            <!-- JAL-3388-->Sorting Structure Chooser table by Sequence
+            ID fails with ClassCastException
+          </li>
+          <li>
+            <!-- JAL-3389 -->Chimera session not restored from Jalview
+            Project
+          </li>
+          <li>
+            <!-- JAL-3441 -->Double-click on 'Show feature' checkbox in
+            feature settings dialog also selects columns
+          </li>
+          <li>
+            <!-- JAL-3473 -->SpinnerNumberModel causing
+            IllegalArgumentException in some circumstances
+          </li>
+          <li>
+            <!-- JAL-3406 -->Credits missing some authors in Jalview
+            help documentation for 2.11.0 release
+          </li>
+        </ul> <em>Java 11 Compatibility issues</em>
         <ul>
           <li>
             <!-- JAL-2987 -->OSX - Can't view results in PDB/Uniprot FTS
           </li>
-        </ul>
-        
-        <em>Repository and Source Release</em>
+        </ul> <em>Repository and Source Release</em>
         <ul>
           <li>
             <!-- JAL-3474 -->removed redundant .gitignore files from
             repository
           </li>
         </ul>
+        <em>New Known Issues</em>
+        <ul>
+          <li>
+            <!-- JAL-3523 -->OSX - Current working directory not
+            preserved when Jalview.app launched with parameters from
+            command line
+          </li>
+          <li>
+            <!--  JAL-3525 -->Sequence IDs aligned to wrong margin and
+            clipped in headless figure export when Right Align option
+            enabled
+          </li>
+        </ul>
       </td>
     </tr>
     <tr>
@@ -104,20 +167,20 @@ li:before {
             versions via (<a href="https://github.com/threerings/getdown">Three
               Rings' GetDown</a>)
           </li>
-                                       <li>
-                                               <!-- JAL-1839,JAL-3254,JAL-3260 -->File type associations for
-                                               formats supported by Jalview (including .jvp project files)
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3260 -->Jalview launch files (.jvl) to pass command line
-                                               arguments and switch between different getdown channels
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3141 -->Backup files created when saving Jalview project
-                                               or alignment files
-                                       </li>
+          <li>
+            <!-- JAL-1839,JAL-3254,JAL-3260 -->File type associations for
+            formats supported by Jalview (including .jvp project files)
+          </li>
+          <li>
+            <!-- JAL-3260 -->Jalview launch files (.jvl) to pass command line
+            arguments and switch between different getdown channels
+          </li>
+          <li>
+            <!-- JAL-3141 -->Backup files created when saving Jalview project
+            or alignment files
+          </li>
 
-                                       <li>
+          <li>
             <!-- JAL-1793 -->Annotate nucleotide alignments from VCF data files</li>
             <li><!-- JAL-2753 -->Version of HTSJDK shipped with Jalview updated to version 2.12.0</li> 
           <li>
@@ -125,9 +188,9 @@ li:before {
             'Translate as cDNA'</li>
           <li>
             <!-- JAL-3018 -->Update of Ensembl Rest Client to API v10.0</li>
-                                       <li><strong>Enhanced visualisation and analysis of Sequence Features</strong>
-                                               <ul>
-                                                         <li>
+          <li><strong>Enhanced visualisation and analysis of Sequence Features</strong>
+            <ul>
+                      <li>
             <!-- JAL-3140 JAL-2446 -->IntervalStoreJ (NCList
             implementation that allows updates) used for Sequence Feature collections</li>
           <li>
@@ -142,124 +205,124 @@ li:before {
                 stored and restored from Jalview Projects
               </li>
               <li>
-                                                               <!-- JAL-3334 -->Use full Sequence Ontology (via BioJava) to
-                                                               recognise variant features
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-2897,JAL-3330 -->Show synonymous codon variants on peptide
-                                                               sequences (also coloured red by default)
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-2792 -->Popup window to show full report for a selected sequence feature's
-                                                               details
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3139,JAL-2816,JAL-1117 -->More efficient sequence feature render
-                                                               algorithm (Z-sort/transparency and filter aware)
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3049,JAL-3054 -->Improved tooltips in Feature Settings
-                                                               dialog
-                                                       </li>
-                                               </ul>
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3205 -->Symmetric score matrices for faster
-                                               tree and PCA calculations
-                                       </li>
-                                       <li><strong>Principal Components Analysis Viewer</strong>
+                <!-- JAL-3334 -->Use full Sequence Ontology (via BioJava) to
+                recognise variant features
+              </li>
+              <li>
+                <!-- JAL-2897,JAL-3330 -->Show synonymous codon variants on peptide
+                sequences (also coloured red by default)
+              </li>
+              <li>
+                <!-- JAL-2792 -->Popup window to show full report for a selected sequence feature's
+                details
+              </li>
+              <li>
+                <!-- JAL-3139,JAL-2816,JAL-1117 -->More efficient sequence feature render
+                algorithm (Z-sort/transparency and filter aware)
+              </li>
+              <li>
+                <!-- JAL-3049,JAL-3054 -->Improved tooltips in Feature Settings
+                dialog
+              </li>
+            </ul>
+          </li>
+          <li>
+            <!-- JAL-3205 -->Symmetric score matrices for faster
+            tree and PCA calculations
+          </li>
+          <li><strong>Principal Components Analysis Viewer</strong>
             <ul>
-                                                       <li>
-                                                               <!-- JAL-1767,JAL-2647 -->Principal Components Analysis results
-                                                               and Viewer state saved in Jalview Project
-                                                       </li>
-                                                       <li><!-- JAL-2962 -->'Change parameters' option removed from viewer's
-                                                               drop-down menus</li>
-                                                       <li>
-                                                               <!-- JAL-2975 -->Can use shift + arrow keys to rotate PCA image
-                                                               incrementally
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-2965, JAL-1285 -->PCA plot is depth cued
-                                                       </li>
-                                               </ul>
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3127 -->New 'Colour by Sequence ID' option
-                                       </li>
-                                       <li><strong>Speed and Efficiency</strong>
-                                       <ul>
-                                                       <li>
-                                                               <!-- JAL-2185,JAL-3198 -->More efficient creation of selections and
-                                                               multiple groups when working with large alignments
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3200 -->Speedier import of annotation rows when parsing
-                                                               Stockholm files
-                                                       </li>
-                                               </ul>
-                                       <li><strong>User Interface</strong>
-                                       <ul>
-                                                       <li>
-                                                               <!-- JAL-2933 -->Finder panel remembers last position in each
-                                                               view
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-2527 JAL-3203 -->Alignment Overview now WYSIWIS (What you see is
-                                                               what is shown)<br />Only visible regions of alignment are shown by
-                                                               default (can be changed in user preferences)
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3169 -->File Chooser stays open after responding Cancel
-                                                               to the Overwrite Dialog
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-2420,JAL-3166 -->Better popup menu behaviour when all
-                                                               sequences are hidden
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-1244 -->Status bar shows bounds when dragging a
-                                                               selection region, and gap count when inserting or deleting gaps
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3132 -->Status bar updates over sequence and annotation
-                                                               labels
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3093 -->Annotation tooltips and popup menus are shown
-                                                               when in wrapped mode
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3073 -->Can select columns by dragging left/right in a graph or histogram
-                                                               annotation
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-2814,JAL-437 -->Help button on Uniprot and PDB search panels
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-2621 -->Cursor changes over draggable box in Overview
-                                                               panel
-                                                       </li>
-                                                       <li>
-                                                               <!-- JAL-3181 -->Consistent ordering of links in sequence id
-                                                               popup menu
-                                                       </li>
-                                                       <li>
-                                                       <!-- JAL-3080 -->Red line indicating tree-cut position not shown if no subgroups are created</li>
-                                                       <li>
-                                                       <!-- JAL-3042 -->Removed ability to configure length of search history by right-clicking search box</li>
-                                                       
-                                                        
-                                               </ul></li>
-                                               <li><!-- JAL-3232 -->Jalview Groovy Scripting Console updated to Groovy v2.5</li> 
-                                       <li><strong>Java 11 Support (not yet on general release)</strong>
-                                               <ul>
-                                                       <li>
-                                                               <!--  -->OSX GUI integrations for App menu's 'About' entry and
-                                                               trapping CMD-Q
-                                                       </li>
-                                               </ul></li>
-                               </ul>
+              <li>
+                <!-- JAL-1767,JAL-2647 -->Principal Components Analysis results
+                and Viewer state saved in Jalview Project
+              </li>
+              <li><!-- JAL-2962 -->'Change parameters' option removed from viewer's
+                drop-down menus</li>
+              <li>
+                <!-- JAL-2975 -->Can use shift + arrow keys to rotate PCA image
+                incrementally
+              </li>
+              <li>
+                <!-- JAL-2965, JAL-1285 -->PCA plot is depth cued
+              </li>
+            </ul>
+          </li>
+          <li>
+            <!-- JAL-3127 -->New 'Colour by Sequence ID' option
+          </li>
+          <li><strong>Speed and Efficiency</strong>
+          <ul>
+              <li>
+                <!-- JAL-2185,JAL-3198 -->More efficient creation of selections and
+                multiple groups when working with large alignments
+              </li>
+              <li>
+                <!-- JAL-3200 -->Speedier import of annotation rows when parsing
+                Stockholm files
+              </li>
+            </ul>
+          <li><strong>User Interface</strong>
+          <ul>
+              <li>
+                <!-- JAL-2933 -->Finder panel remembers last position in each
+                view
+              </li>
+              <li>
+                <!-- JAL-2527 JAL-3203 -->Alignment Overview now WYSIWIS (What you see is
+                what is shown)<br />Only visible regions of alignment are shown by
+                default (can be changed in user preferences)
+              </li>
+              <li>
+                <!-- JAL-3169 -->File Chooser stays open after responding Cancel
+                to the Overwrite Dialog
+              </li>
+              <li>
+                <!-- JAL-2420,JAL-3166 -->Better popup menu behaviour when all
+                sequences are hidden
+              </li>
+              <li>
+                <!-- JAL-1244 -->Status bar shows bounds when dragging a
+                selection region, and gap count when inserting or deleting gaps
+              </li>
+              <li>
+                <!-- JAL-3132 -->Status bar updates over sequence and annotation
+                labels
+              </li>
+              <li>
+                <!-- JAL-3093 -->Annotation tooltips and popup menus are shown
+                when in wrapped mode
+              </li>
+              <li>
+                <!-- JAL-3073 -->Can select columns by dragging left/right in a graph or histogram
+                annotation
+              </li>
+              <li>
+                <!-- JAL-2814,JAL-437 -->Help button on Uniprot and PDB search panels
+              </li>
+              <li>
+                <!-- JAL-2621 -->Cursor changes over draggable box in Overview
+                panel
+              </li>
+              <li>
+                <!-- JAL-3181 -->Consistent ordering of links in sequence id
+                popup menu
+              </li>
+              <li>
+              <!-- JAL-3080 -->Red line indicating tree-cut position not shown if no subgroups are created</li>
+              <li>
+              <!-- JAL-3042 -->Removed ability to configure length of search history by right-clicking search box</li>
+              
+               
+            </ul></li>
+            <li><!-- JAL-3232 -->Jalview Groovy Scripting Console updated to Groovy v2.5</li> 
+          <li><strong>Java 11 Support (not yet on general release)</strong>
+            <ul>
+              <li>
+                <!--  -->OSX GUI integrations for App menu's 'About' entry and
+                trapping CMD-Q
+              </li>
+            </ul></li>
+        </ul>
         <em>Deprecations</em>
         <ul>
           <li><!-- JAL-3035 -->DAS sequence retrieval and annotation
@@ -271,18 +334,18 @@ li:before {
           <li><!-- JAL-3311 -->Disable VAMSAS menu in preparation for removal</li> 
           <li><!--  -->Jalview Desktop no longer distributed via Java Web Start</li>
         </ul> <em>Documentation</em>
-                               <ul>
-                                       <li><!-- JAL-3003 -->Added remarks about transparent rendering effects
-                                               not supported in EPS figure export
-                                       </li>
-                                       <li><!-- JAL-2903 -->Typos in documentation for Preferences dialog</li>
-                               </ul> <em>Development and Release Processes</em>
-        <ul>
-               <li>
-               <!-- JAL-3196,JAL-3179.JAL-2671 -->Build system migrated from Ant to Gradle
-                                       </li>
-                       <li>
-                       <!-- JAL-1424 -->Enhanced checks for missing and duplicated keys in Message bundles</li>
+        <ul>
+          <li><!-- JAL-3003 -->Added remarks about transparent rendering effects
+            not supported in EPS figure export
+          </li>
+          <li><!-- JAL-2903 -->Typos in documentation for Preferences dialog</li>
+        </ul> <em>Development and Release Processes</em>
+        <ul>
+          <li>
+          <!-- JAL-3196,JAL-3179.JAL-2671 -->Build system migrated from Ant to Gradle
+          </li>
+      <li>
+      <!-- JAL-1424 -->Enhanced checks for missing and duplicated keys in Message bundles</li>
           <li>
           <!-- JAL-3225 -->Eclipse project configuration managed with
             gradle-eclipse
@@ -313,194 +376,194 @@ li:before {
           </li>
         </ul>
       </td>
-                       <td align="left" valign="top">
-                               <ul>
-                                       <li>
-                                               <!-- JAL-3143 -->Timeouts when retrieving data from Ensembl
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3244 -->'View [Structure] Mappings' and structure
-                                               superposition in Jmol fail on Windows
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3286 -->Blank error dialog is displayed when discovering
-                                               structures for sequences with lots of PDB structures
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3239 -->Text misaligned in EPS or SVG image export with
-                                               monospaced font
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3171 -->Warning of 'Duplicate entry' when saving Jalview
-                                               project involving multiple views
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3164 -->Overview for complementary view in a linked
-                                               CDS/Protein alignment is not updated when Hide Columns by
-                                               Annotation dialog hides columns
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3158 -->Selection highlighting in the complement of a
-                                               CDS/Protein alignment stops working after making a selection in
-                                               one view, then making another selection in the other view
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3161 -->Annotations tooltip changes beyond visible
-                                               columns
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3154 -->Table Columns could be re-ordered in Feature
-                                               Settings and Jalview Preferences panels
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2865 -->Jalview hangs when closing windows, or redrawing the
-                                               overview with large alignments
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2750 -->Tree and PCA calculation fails for selected
-                                               region if columns were selected by dragging right-to-left and the
-                                               mouse moved to the left of the first column
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3218 -->Couldn't hide selected columns adjacent to a
-                                               hidden column marker via scale popup menu
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2846 -->Error message for trying to load in invalid URLs
-                                               doesn't tell users the invalid URL
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2816 -->Tooltips displayed for features filtered by
-                                               score from view
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3330 -->Sequence Variants retrieved from Ensembl during
-                                               show cross references or Fetch Database References are shown in
-                                               red in original view
-                                       </li>
+      <td align="left" valign="top">
+        <ul>
+          <li>
+            <!-- JAL-3143 -->Timeouts when retrieving data from Ensembl
+          </li>
+          <li>
+            <!-- JAL-3244 -->'View [Structure] Mappings' and structure
+            superposition in Jmol fail on Windows
+          </li>
+          <li>
+            <!-- JAL-3286 -->Blank error dialog is displayed when discovering
+            structures for sequences with lots of PDB structures
+          </li>
+          <li>
+            <!-- JAL-3239 -->Text misaligned in EPS or SVG image export with
+            monospaced font
+          </li>
+          <li>
+            <!-- JAL-3171 -->Warning of 'Duplicate entry' when saving Jalview
+            project involving multiple views
+          </li>
+          <li>
+            <!-- JAL-3164 -->Overview for complementary view in a linked
+            CDS/Protein alignment is not updated when Hide Columns by
+            Annotation dialog hides columns
+          </li>
+          <li>
+            <!-- JAL-3158 -->Selection highlighting in the complement of a
+            CDS/Protein alignment stops working after making a selection in
+            one view, then making another selection in the other view
+          </li>
+          <li>
+            <!-- JAL-3161 -->Annotations tooltip changes beyond visible
+            columns
+          </li>
+          <li>
+            <!-- JAL-3154 -->Table Columns could be re-ordered in Feature
+            Settings and Jalview Preferences panels
+          </li>
+          <li>
+            <!-- JAL-2865 -->Jalview hangs when closing windows, or redrawing the
+            overview with large alignments
+          </li>
+          <li>
+            <!-- JAL-2750 -->Tree and PCA calculation fails for selected
+            region if columns were selected by dragging right-to-left and the
+            mouse moved to the left of the first column
+          </li>
+          <li>
+            <!-- JAL-3218 -->Couldn't hide selected columns adjacent to a
+            hidden column marker via scale popup menu
+          </li>
+          <li>
+            <!-- JAL-2846 -->Error message for trying to load in invalid URLs
+            doesn't tell users the invalid URL
+          </li>
+          <li>
+            <!-- JAL-2816 -->Tooltips displayed for features filtered by
+            score from view
+          </li>
+          <li>
+            <!-- JAL-3330 -->Sequence Variants retrieved from Ensembl during
+            show cross references or Fetch Database References are shown in
+            red in original view
+          </li>
           <li>
             <!-- JAL-2898,JAL-2207 -->stop_gained variants not shown correctly on
             peptide sequence (computed variant shown as p.Res.null)
           </li>
-                                       <li>
-                                               <!-- JAL-2060 -->'Graduated colour' option not offered for
-                                               manually created features (where feature score is Float.NaN)
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3097,JAL-3099 -->Blank extra columns drawn or printed
-                                               when columns are hidden
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3082 -->Regular expression error for '(' in Select
-                                               Columns by Annotation description
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3072 -->Scroll doesn't stop on mouse up after dragging
-                                               out of Scale or Annotation Panel
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3075 -->Column selection incorrect after scrolling out of
-                                               scale panel
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3074 -->Left/right drag in annotation can scroll
-                                               alignment down
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3108 -->Error if mouse moved before clicking Reveal in
-                                               scale panel
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3002 -->Column display is out by one after Page Down,
-                                               Page Up in wrapped mode
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2839,JAL-781 -->Finder doesn't skip hidden regions
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2932 -->Finder searches in minimised alignments
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2250 -->'Apply Colour to All Groups' not always selected
-                                               on opening an alignment
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3180 -->'Colour by Annotation' not marked selected in
-                                               Colour menu
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3201 -->Per-group Clustal colour scheme changes when
-                                               different groups in the alignment are selected
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2717 -->Internationalised colour scheme names not shown
-                                               correctly in menu
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3206 -->Colour by Annotation can go black at min/max
-                                               threshold limit
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3125 -->Value input for graduated feature colour
-                                               threshold gets 'unrounded'
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2982 -->PCA image export doesn't respect background
-                                               colour
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2963 -->PCA points don't dim when rotated about y axis
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2959 -->PCA Print dialog continues after Cancel
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3078 -->Cancel in Tree Font dialog resets alignment, not
-                                               Tree font
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2964 -->Associate Tree with All Views not restored from
-                                               project file
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2915 -->Scrolling of split frame is sluggish if Overview
-                                               shown in complementary view
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3313 -->Codon consensus incorrectly scaled when shown
-                                               without normalisation
-                                       </li>
-                                       <li>
-                                               <!-- JAL-3021 -->Sequence Details report should open positioned at top
-                                               of report
-                                       </li>
-                                       <li>
-                                               <!-- JAL-914 -->Help page can be opened twice
-                                       </li>
-                                 <li>
-                                 <!-- JAL-3333 -->Fuzzy text in web service status menu on OSX Mojave
-                                 </li>
-                               </ul> <em>Editing</em>
-                               <ul>
-                                       <li>
-                                               <!-- JAL-2822 -->Start and End should be updated when sequence
-                                               data at beginning or end of alignment added/removed via 'Edit'
-                                               sequence
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2541,JAL-2684 (tests) -->Delete/Cut selection doesn't
-                                               relocate sequence features correctly when start of sequence is
-                                               removed (Known defect since 2.10)
-                                       </li>
-                                       <li>
-                                               <!-- JAL-2830 -->Inserting gap sequence via the Edit Sequence
-                                               dialog corrupts dataset sequence
-                                       </li>
-                                       <li>
-                                               <!-- JAL-868 -->Structure colours not updated when associated tree
-                                               repartitions the alignment view (Regression in 2.10.5)
-                                       </li>
-                               </ul> <em>Datamodel</em>
+          <li>
+            <!-- JAL-2060 -->'Graduated colour' option not offered for
+            manually created features (where feature score is Float.NaN)
+          </li>
+          <li>
+            <!-- JAL-3097,JAL-3099 -->Blank extra columns drawn or printed
+            when columns are hidden
+          </li>
+          <li>
+            <!-- JAL-3082 -->Regular expression error for '(' in Select
+            Columns by Annotation description
+          </li>
+          <li>
+            <!-- JAL-3072 -->Scroll doesn't stop on mouse up after dragging
+            out of Scale or Annotation Panel
+          </li>
+          <li>
+            <!-- JAL-3075 -->Column selection incorrect after scrolling out of
+            scale panel
+          </li>
+          <li>
+            <!-- JAL-3074 -->Left/right drag in annotation can scroll
+            alignment down
+          </li>
+          <li>
+            <!-- JAL-3108 -->Error if mouse moved before clicking Reveal in
+            scale panel
+          </li>
+          <li>
+            <!-- JAL-3002 -->Column display is out by one after Page Down,
+            Page Up in wrapped mode
+          </li>
+          <li>
+            <!-- JAL-2839,JAL-781 -->Finder doesn't skip hidden regions
+          </li>
+          <li>
+            <!-- JAL-2932 -->Finder searches in minimised alignments
+          </li>
+          <li>
+            <!-- JAL-2250 -->'Apply Colour to All Groups' not always selected
+            on opening an alignment
+          </li>
+          <li>
+            <!-- JAL-3180 -->'Colour by Annotation' not marked selected in
+            Colour menu
+          </li>
+          <li>
+            <!-- JAL-3201 -->Per-group Clustal colour scheme changes when
+            different groups in the alignment are selected
+          </li>
+          <li>
+            <!-- JAL-2717 -->Internationalised colour scheme names not shown
+            correctly in menu
+          </li>
+          <li>
+            <!-- JAL-3206 -->Colour by Annotation can go black at min/max
+            threshold limit
+          </li>
+          <li>
+            <!-- JAL-3125 -->Value input for graduated feature colour
+            threshold gets 'unrounded'
+          </li>
+          <li>
+            <!-- JAL-2982 -->PCA image export doesn't respect background
+            colour
+          </li>
+          <li>
+            <!-- JAL-2963 -->PCA points don't dim when rotated about y axis
+          </li>
+          <li>
+            <!-- JAL-2959 -->PCA Print dialog continues after Cancel
+          </li>
+          <li>
+            <!-- JAL-3078 -->Cancel in Tree Font dialog resets alignment, not
+            Tree font
+          </li>
+          <li>
+            <!-- JAL-2964 -->Associate Tree with All Views not restored from
+            project file
+          </li>
+          <li>
+            <!-- JAL-2915 -->Scrolling of split frame is sluggish if Overview
+            shown in complementary view
+          </li>
+          <li>
+            <!-- JAL-3313 -->Codon consensus incorrectly scaled when shown
+            without normalisation
+          </li>
+          <li>
+            <!-- JAL-3021 -->Sequence Details report should open positioned at top
+            of report
+          </li>
+          <li>
+            <!-- JAL-914 -->Help page can be opened twice
+          </li>
+          <li>
+          <!-- JAL-3333 -->Fuzzy text in web service status menu on OSX Mojave
+          </li>
+        </ul> <em>Editing</em>
+        <ul>
+          <li>
+            <!-- JAL-2822 -->Start and End should be updated when sequence
+            data at beginning or end of alignment added/removed via 'Edit'
+            sequence
+          </li>
+          <li>
+            <!-- JAL-2541,JAL-2684 (tests) -->Delete/Cut selection doesn't
+            relocate sequence features correctly when start of sequence is
+            removed (Known defect since 2.10)
+          </li>
+          <li>
+            <!-- JAL-2830 -->Inserting gap sequence via the Edit Sequence
+            dialog corrupts dataset sequence
+          </li>
+          <li>
+            <!-- JAL-868 -->Structure colours not updated when associated tree
+            repartitions the alignment view (Regression in 2.10.5)
+          </li>
+        </ul> <em>Datamodel</em>
         <ul>
           <li>
             <!-- JAL-2986 -->Sequence.findIndex returns wrong value when
@@ -514,41 +577,41 @@ li:before {
           </li>
         </ul> <em>New Known Defects</em>
         <ul>
-                               <li>
-                               <!-- JAL-3340 -->Select columns containing feature by double clicking ignores bounds of an existing selected region
-                               </li>
-                               <li>
-                                       <!-- JAL-3313 -->Codon consensus logo incorrectly scaled in gapped
-                                       regions of protein alignment.
-                               </li>
-                               <li>
-                                       <!-- JAL-2647 -->Input Data menu entry is greyed out when PCA View
-                                       is restored from a Jalview 2.11 project
-                               </li>
-                               <li>
-                                       <!-- JAL-3213 -->Alignment panel height can be too small after
-                                       'New View'
-                               </li>
-                               <li>
-                                       <!-- JAL-3240 -->Display is incorrect after removing gapped
-                                       columns within hidden columns
-                               </li>
-                               <li>
-                                       <!-- JAL-3314 -->Rightmost selection is lost when mouse re-enters
-                                       window after dragging left to select columns to left of visible
-                                       region
-                               </li>
-                               <li>
-                                       <!-- JAL-2876 -->Features coloured according to their description
-                                       string and thresholded by score in earlier versions of Jalview are
-                                       not shown as thresholded features in 2.11. To workaround please
-                                       create a Score filter instead.
-                               </li>
-                               <li>
-                               <!-- JAL-3184 -->Cancel on Feature Settings dialog doesn't reset group visibility</li> 
-                               <li>
-                               <!-- JAL-3338 -->F2 doesn't enable/disable keyboard mode in linked CDS/Protein view
-                               </li>
+        <li>
+        <!-- JAL-3340 -->Select columns containing feature by double clicking ignores bounds of an existing selected region
+        </li>
+        <li>
+          <!-- JAL-3313 -->Codon consensus logo incorrectly scaled in gapped
+          regions of protein alignment.
+        </li>
+        <li>
+          <!-- JAL-2647 -->Input Data menu entry is greyed out when PCA View
+          is restored from a Jalview 2.11 project
+        </li>
+        <li>
+          <!-- JAL-3213 -->Alignment panel height can be too small after
+          'New View'
+        </li>
+        <li>
+          <!-- JAL-3240 -->Display is incorrect after removing gapped
+          columns within hidden columns
+        </li>
+        <li>
+          <!-- JAL-3314 -->Rightmost selection is lost when mouse re-enters
+          window after dragging left to select columns to left of visible
+          region
+        </li>
+        <li>
+          <!-- JAL-2876 -->Features coloured according to their description
+          string and thresholded by score in earlier versions of Jalview are
+          not shown as thresholded features in 2.11. To workaround please
+          create a Score filter instead.
+        </li>
+        <li>
+        <!-- JAL-3184 -->Cancel on Feature Settings dialog doesn't reset group visibility</li> 
+        <li>
+        <!-- JAL-3338 -->F2 doesn't enable/disable keyboard mode in linked CDS/Protein view
+        </li>
         <li>
           <!-- JAL-797 -->Closing tree windows with CMD/CTRL-W for
           alignments with multiple views can close views unexpectedly
@@ -562,7 +625,7 @@ li:before {
             </li>
         </ul>
       </td>
-               </tr>
+    </tr>
     <tr>
     <td width="60" nowrap>
       <div align="center">
@@ -898,11 +961,11 @@ li:before {
               Sequences' enabled) or Ensembl isoforms (Workaround in
               2.10.4 is to fail back to N&amp;W mapping)
             </li>
-                                               <li>
-                                                       <!-- JAL-2990 -->Export Annotations from File Menu with CSV
-                                                       option gives blank output
-                                               </li>
-                                       </ul>
+            <li>
+              <!-- JAL-2990 -->Export Annotations from File Menu with CSV
+              option gives blank output
+            </li>
+          </ul>
         </div>
           </td>
     </tr>
index 4b406be..42cb24f 100755 (executable)
 </head>
 <body>
   <p>
+    <strong>Jalview 2.11.1</strong>
+  </p>
+  <p>
+    Jalview 2.11.1.0 is the first minor release for the 2.11 series.
+    We've moved to a <em>four</em> number scheme to help you (and us!)
+    keep track of patch and bug fix releases (which we used to denote
+    with a 'b').
+  </p>
+  <ul>
+    <li><strong><a href="features/importvcf.html#attribs">Standard
+          attributes for filtering variants</a></strong> (e.g. position, QUAL field etc) a new feature suggested by
+      a user at the Jalview booth during ISMB 2019.</li>
+  </ul>
+  <p>
+    See the <a href="releases.html#Jalview.2.11.1.0">2.11.1.0
+      release notes</a> for full details of bugs fixed and new known issues.
+  </p>
+  <p>
     <strong>Jalview 2.11 - new installer and new capabilities</strong>
   </p>
   <p>Jalview 2.11 introduces support for loading VCF files, and new
index 55efaa5..fdf66d0 100644 (file)
@@ -2389,97 +2389,6 @@ public class AlignmentUtils
   }
 
   /**
-   * Helper method that adds a peptide variant feature. ID and
-   * clinical_significance attributes of the dna variant (if present) are copied
-   * to the new feature.
-   * 
-   * @param peptide
-   * @param peptidePos
-   * @param residue
-   * @param var
-   * @param codon
-   *          the variant codon e.g. aCg
-   * @param canonical
-   *          the 'normal' codon e.g. aTg
-   * @return true if a feature was added, else false
-   */
-  static boolean addPeptideVariant(SequenceI peptide, int peptidePos,
-          String residue, DnaVariant var, String codon, String canonical)
-  {
-    /*
-     * get peptide translation of codon e.g. GAT -> D
-     * note that variants which are not single alleles,
-     * e.g. multibase variants or HGMD_MUTATION etc
-     * are currently ignored here
-     */
-    String trans = codon.contains("-") ? null
-            : (codon.length() > CODON_LENGTH ? null
-                    : ResidueProperties.codonTranslate(codon));
-    if (trans == null)
-    {
-      return false;
-    }
-    String desc = canonical + "/" + codon;
-    String featureType = "";
-    if (trans.equals(residue))
-    {
-      featureType = SequenceOntologyI.SYNONYMOUS_VARIANT;
-    }
-    else if (ResidueProperties.STOP.equals(trans))
-    {
-      featureType = SequenceOntologyI.STOP_GAINED;
-    }
-    else
-    {
-      String residue3Char = StringUtils
-              .toSentenceCase(ResidueProperties.aa2Triplet.get(residue));
-      String trans3Char = StringUtils
-              .toSentenceCase(ResidueProperties.aa2Triplet.get(trans));
-      desc = "p." + residue3Char + peptidePos + trans3Char;
-      featureType = SequenceOntologyI.NONSYNONYMOUS_VARIANT;
-    }
-    SequenceFeature sf = new SequenceFeature(featureType, desc, peptidePos,
-            peptidePos, var.getSource());
-
-    StringBuilder attributes = new StringBuilder(32);
-    String id = (String) var.variant.getValue(VARIANT_ID);
-    if (id != null)
-    {
-      if (id.startsWith(SEQUENCE_VARIANT))
-      {
-        id = id.substring(SEQUENCE_VARIANT.length());
-      }
-      sf.setValue(VARIANT_ID, id);
-      attributes.append(VARIANT_ID).append("=").append(id);
-      // TODO handle other species variants JAL-2064
-      StringBuilder link = new StringBuilder(32);
-      try
-      {
-        link.append(desc).append(" ").append(id).append(
-                "|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=")
-                .append(URLEncoder.encode(id, "UTF-8"));
-        sf.addLink(link.toString());
-      } catch (UnsupportedEncodingException e)
-      {
-        // as if
-      }
-    }
-    String clinSig = (String) var.variant.getValue(CLINICAL_SIGNIFICANCE);
-    if (clinSig != null)
-    {
-      sf.setValue(CLINICAL_SIGNIFICANCE, clinSig);
-      attributes.append(";").append(CLINICAL_SIGNIFICANCE).append("=")
-              .append(clinSig);
-    }
-    peptide.addSequenceFeature(sf);
-    if (attributes.length() > 0)
-    {
-      sf.setAttributes(attributes.toString());
-    }
-    return true;
-  }
-
-  /**
    * Makes an alignment with a copy of the given sequences, adding in any
    * non-redundant sequences which are mapped to by the cross-referenced
    * sequences.
index c8a7def..2dd9cf0 100755 (executable)
@@ -28,7 +28,7 @@ import jalview.datamodel.features.FeatureSources;
 import jalview.util.StringUtils;
 
 import java.util.Comparator;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.SortedMap;
@@ -50,10 +50,10 @@ public class SequenceFeature implements FeatureLocationI
 
   private static final String STATUS = "status";
 
-  private static final String STRAND = "STRAND";
+  public static final String STRAND = "STRAND";
 
-  // private key for Phase designed not to conflict with real GFF data
-  private static final String PHASE = "!Phase";
+  // key for Phase designed not to conflict with real GFF data
+  public static final String PHASE = "!Phase";
 
   // private key for ENA location designed not to conflict with real GFF data
   private static final String LOCATION = "!Location";
@@ -61,12 +61,6 @@ public class SequenceFeature implements FeatureLocationI
   private static final String ROW_DATA = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>";
 
   /*
-   * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as
-   * name1=value1;name2=value2,value3;...etc
-   */
-  private static final String ATTRIBUTES = "ATTRIBUTES";
-
-  /*
    * type, begin, end, featureGroup, score and contactFeature are final 
    * to ensure that the integrity of SequenceFeatures data store 
    * can't be broken by direct update of these fields
@@ -174,19 +168,13 @@ public class SequenceFeature implements FeatureLocationI
 
     if (sf.otherDetails != null)
     {
-      otherDetails = new HashMap<>();
-      for (Entry<String, Object> entry : sf.otherDetails.entrySet())
-      {
-        otherDetails.put(entry.getKey(), entry.getValue());
-      }
+      otherDetails = new LinkedHashMap<>();
+      otherDetails.putAll(sf.otherDetails);
     }
     if (sf.links != null && sf.links.size() > 0)
     {
       links = new Vector<>();
-      for (int i = 0, iSize = sf.links.size(); i < iSize; i++)
-      {
-        links.addElement(sf.links.elementAt(i));
-      }
+      links.addAll(sf.links);
     }
   }
 
@@ -440,7 +428,10 @@ public class SequenceFeature implements FeatureLocationI
     {
       if (otherDetails == null)
       {
-        otherDetails = new HashMap<>();
+        /*
+         * LinkedHashMap preserves insertion order of attributes
+         */
+        otherDetails = new LinkedHashMap<>();
       }
 
       otherDetails.put(key, value);
@@ -483,16 +474,6 @@ public class SequenceFeature implements FeatureLocationI
     return (String) getValue(STATUS);
   }
 
-  public void setAttributes(String attr)
-  {
-    setValue(ATTRIBUTES, attr);
-  }
-
-  public String getAttributes()
-  {
-    return (String) getValue(ATTRIBUTES);
-  }
-
   /**
    * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in
    * GFF), and 0 for unknown or not (validly) specified
@@ -643,10 +624,6 @@ public class SequenceFeature implements FeatureLocationI
       for (Entry<String, Object> entry : ordered.entrySet())
       {
         String key = entry.getKey();
-        if (ATTRIBUTES.equals(key))
-        {
-          continue; // to avoid double reporting
-        }
 
         Object value = entry.getValue();
         if (value instanceof Map<?, ?>)
index 001e18e..b22b9c7 100644 (file)
@@ -724,18 +724,6 @@ public abstract class EnsemblSeqProxy extends EnsemblRestClient
     String comp = complement.toString();
     sf.setValue(Gff3Helper.ALLELES, comp);
     sf.setDescription(comp);
-
-    /*
-     * replace value of "alleles=" in sf.ATTRIBUTES as well
-     * so 'output as GFF' shows reverse complement alleles
-     */
-    String atts = sf.getAttributes();
-    if (atts != null)
-    {
-      atts = atts.replace(Gff3Helper.ALLELES + "=" + alleles,
-              Gff3Helper.ALLELES + "=" + comp);
-      sf.setAttributes(atts);
-    }
   }
 
   /**
index a69788b..a8a3746 100755 (executable)
@@ -36,7 +36,6 @@ import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.FeatureMatcherSet;
 import jalview.datamodel.features.FeatureMatcherSetI;
 import jalview.gui.Desktop;
-import jalview.io.gff.GffHelperBase;
 import jalview.io.gff.GffHelperFactory;
 import jalview.io.gff.GffHelperI;
 import jalview.schemes.FeatureColour;
@@ -75,6 +74,8 @@ import java.util.TreeMap;
  */
 public class FeaturesFile extends AlignFile implements FeaturesSourceI
 {
+  private static final String EQUALS = "=";
+
   private static final String TAB_REGEX = "\\t";
 
   private static final String STARTGROUP = "STARTGROUP";
@@ -87,8 +88,6 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
 
   private static final String ID_NOT_SPECIFIED = "ID_NOT_SPECIFIED";
 
-  private static final String NOTE = "Note";
-
   protected static final String GFF_VERSION = "##gff-version";
 
   private AlignmentI lastmatchedAl = null;
@@ -1126,11 +1125,110 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
     String phase = sf.getPhase();
     out.append(phase == null ? "." : phase);
 
-    // miscellaneous key-values (GFF column 9)
-    String attributes = sf.getAttributes();
-    if (attributes != null)
+    if (sf.otherDetails != null && !sf.otherDetails.isEmpty())
+    {
+      Map<String, Object> map = sf.otherDetails;
+      formatAttributes(out, map);
+    }
+  }
+
+  /**
+   * A helper method that outputs attributes stored in the map as
+   * semicolon-delimited values e.g.
+   * 
+   * <pre>
+   * AC_Male=0;AF_NFE=0.00000e 00;Hom_FIN=0;GQ_MEDIAN=9
+   * </pre>
+   * 
+   * A map-valued attribute is formatted as a comma-delimited list within braces,
+   * for example
+   * 
+   * <pre>
+   * jvmap_CSQ={ALLELE_NUM=1,UNIPARC=UPI0002841053,Feature=ENST00000585561}
+   * </pre>
+   * 
+   * The {@code jvmap_} prefix designates a values map and is removed if the value
+   * is parsed when read in. (The GFF3 specification allows 'semi-structured data'
+   * to be represented provided the attribute name begins with a lower case
+   * letter.)
+   * 
+   * @param sb
+   * @param map
+   * @see http://gmod.org/wiki/GFF3#GFF3_Format
+   */
+  void formatAttributes(StringBuilder sb, Map<String, Object> map)
+  {
+    sb.append(TAB);
+    boolean first = true;
+    for (String key : map.keySet())
+    {
+      if (SequenceFeature.STRAND.equals(key)
+              || SequenceFeature.PHASE.equals(key))
+      {
+        /*
+         * values stashed in map but output to their own columns
+         */
+        continue;
+      }
+      {
+        if (!first)
+        {
+          sb.append(";");
+        }
+      }
+      first = false;
+      Object value = map.get(key);
+      if (value instanceof Map<?, ?>)
+      {
+        formatMapAttribute(sb, key, (Map<?, ?>) value);
+      }
+      else
+      {
+        String formatted = StringUtils.urlEncode(value.toString(),
+                GffHelperI.GFF_ENCODABLE);
+        sb.append(key).append(EQUALS).append(formatted);
+      }
+    }
+  }
+
+  /**
+   * Formats the map entries as
+   * 
+   * <pre>
+   * key=key1=value1,key2=value2,...
+   * </pre>
+   * 
+   * and appends this to the string buffer
+   * 
+   * @param sb
+   * @param key
+   * @param map
+   */
+  private void formatMapAttribute(StringBuilder sb, String key,
+          Map<?, ?> map)
+  {
+    if (map == null || map.isEmpty())
+    {
+      return;
+    }
+
+    /*
+     * AbstractMap.toString would be a shortcut here, but more reliable
+     * to code the required format in case toString changes in future
+     */
+    sb.append(key).append(EQUALS);
+    boolean first = true;
+    for (Entry<?, ?> entry : map.entrySet())
     {
-      out.append(TAB).append(attributes);
+      if (!first)
+      {
+        sb.append(",");
+      }
+      first = false;
+      sb.append(entry.getKey().toString()).append(EQUALS);
+      String formatted = StringUtils.urlEncode(entry.getValue().toString(),
+              GffHelperI.GFF_ENCODABLE);
+      sb.append(formatted);
     }
   }
 
@@ -1139,11 +1237,11 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
    * format)
    * 
    * @param alignedRegions
-   *          a list of "Align fromStart toStart fromCount"
+   *                         a list of "Align fromStart toStart fromCount"
    * @param mapIsFromCdna
-   *          if true, 'from' is dna, else 'from' is protein
+   *                         if true, 'from' is dna, else 'from' is protein
    * @param strand
-   *          either 1 (forward) or -1 (reverse)
+   *                         either 1 (forward) or -1 (reverse)
    * @return
    * @throws IOException
    */
@@ -1279,38 +1377,6 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
   }
 
   /**
-   * Process the 'column 9' data of the GFF file. This is less formally defined,
-   * and its interpretation will vary depending on the tool that has generated
-   * it.
-   * 
-   * @param attributes
-   * @param sf
-   */
-  protected void processGffColumnNine(String attributes, SequenceFeature sf)
-  {
-    sf.setAttributes(attributes);
-
-    /*
-     * Parse attributes in column 9 and add them to the sequence feature's 
-     * 'otherData' table; use Note as a best proxy for description
-     */
-    char nameValueSeparator = gffVersion == 3 ? '=' : ' ';
-    // TODO check we don't break GFF2 values which include commas here
-    Map<String, List<String>> nameValues = GffHelperBase
-            .parseNameValuePairs(attributes, ";", nameValueSeparator, ",");
-    for (Entry<String, List<String>> attr : nameValues.entrySet())
-    {
-      String values = StringUtils.listToDelimitedString(attr.getValue(),
-              "; ");
-      sf.setValue(attr.getKey(), values);
-      if (NOTE.equals(attr.getKey()))
-      {
-        sf.setDescription(values);
-      }
-    }
-  }
-
-  /**
    * After encountering ##fasta in a GFF3 file, process the remainder of the
    * file as FAST sequence data. Any placeholder sequences created during
    * feature parsing are updated with the actual sequences.
index 19045d5..a15a116 100644 (file)
@@ -38,20 +38,10 @@ public class Gff2Helper extends GffHelperBase
    */
   public static Map<String, List<String>> parseNameValuePairs(String text)
   {
-    // TODO: can a value include a comma? if so it will be broken by this
     return parseNameValuePairs(text, ";", ' ', ",");
   }
 
   /**
-   * Return ' ' as the name-value separator used in column 9 attributes.
-   */
-  @Override
-  protected char getNameValueSeparator()
-  {
-    return ' ';
-  }
-
-  /**
    * Default processing if not overridden is just to construct a sequence
    * feature
    */
index a25a014..1ef8848 100644 (file)
@@ -350,15 +350,6 @@ public class Gff3Helper extends GffHelperBase
   }
 
   /**
-   * Return '=' as the name-value separator used in column 9 attributes.
-   */
-  @Override
-  protected char getNameValueSeparator()
-  {
-    return '=';
-  }
-
-  /**
    * Modifies the default SequenceFeature in order to set the Target sequence id
    * as the description
    */
@@ -424,6 +415,11 @@ public class Gff3Helper extends GffHelperBase
       desc = (String) sf.getValue(ID);
     }
 
+    /*
+     * and decode comma, equals, semi-colon as required by GFF3 spec
+     */
+    desc = StringUtils.urlDecode(desc, GFF_ENCODABLE);
+
     return desc;
   }
 }
index 1d4d3ac..3db1755 100644 (file)
@@ -43,7 +43,13 @@ import java.util.Map.Entry;
  */
 public abstract class GffHelperBase implements GffHelperI
 {
-  private static final String NOTE = "Note";
+  private static final String INVALID_GFF_ATTRIBUTE_FORMAT = "Invalid GFF attribute format: ";
+
+  protected static final String COMMA = ",";
+
+  protected static final String EQUALS = "=";
+
+  protected static final String NOTE = "Note";
 
   /*
    * GFF columns 1-9 (zero-indexed):
@@ -260,9 +266,12 @@ public abstract class GffHelperBase implements GffHelperI
 
   /**
    * Parses the input line to a map of name / value(s) pairs. For example the
-   * line <br>
+   * line
+   * 
+   * <pre>
    * Notes=Fe-S;Method=manual curation, prediction; source = Pfam; Notes = Metal
-   * <br>
+   * </pre>
+   * 
    * if parsed with delimiter=";" and separators {' ', '='} <br>
    * would return a map with { Notes={Fe=S, Metal}, Method={manual curation,
    * prediction}, source={Pfam}} <br>
@@ -272,57 +281,80 @@ public abstract class GffHelperBase implements GffHelperI
    * name), or GFF3 format (which uses '=' as the name/value delimiter, and
    * strictly does not allow repeat occurrences of the same name - but does
    * allow a comma-separated list of values).
+   * <p>
+   * Returns a (possibly empty) map of lists of values by attribute name.
    * 
    * @param text
    * @param namesDelimiter
    *          the major delimiter between name-value pairs
    * @param nameValueSeparator
-   *          one or more separators used between name and value
+   *          separator used between name and value
    * @param valuesDelimiter
    *          delimits a list of more than one value
-   * @return the name-values map (which may be empty but never null)
+   * @return
    */
   public static Map<String, List<String>> parseNameValuePairs(String text,
           String namesDelimiter, char nameValueSeparator,
           String valuesDelimiter)
   {
-    Map<String, List<String>> map = new HashMap<String, List<String>>();
+    Map<String, List<String>> map = new HashMap<>();
     if (text == null || text.trim().length() == 0)
     {
       return map;
     }
 
-    for (String pair : text.trim().split(namesDelimiter))
+    /*
+     * split by major delimiter (; for GFF3)
+     */
+    for (String nameValuePair : text.trim().split(namesDelimiter))
     {
-      pair = pair.trim();
-      if (pair.length() == 0)
+      nameValuePair = nameValuePair.trim();
+      if (nameValuePair.length() == 0)
       {
         continue;
       }
 
-      int sepPos = pair.indexOf(nameValueSeparator);
+      /*
+       * find name/value separator (= for GFF3)
+       */
+      int sepPos = nameValuePair.indexOf(nameValueSeparator);
       if (sepPos == -1)
       {
-        // no name=value present
+        // no name=value found
         continue;
       }
 
-      String key = pair.substring(0, sepPos).trim();
-      String values = pair.substring(sepPos + 1).trim();
-      if (values.length() > 0)
+      String name = nameValuePair.substring(0, sepPos).trim();
+      String values = nameValuePair.substring(sepPos + 1).trim();
+      if (values.isEmpty())
+      {
+        continue;
+      }
+
+      List<String> vals = map.get(name);
+      if (vals == null)
+      {
+        vals = new ArrayList<>();
+        map.put(name, vals);
+      }
+
+      /*
+       * if 'values' contains more name/value separators, parse as a map
+       * (nested sub-attribute values)
+       */
+      if (values.indexOf(nameValueSeparator) != -1)
+      {
+        vals.add(values);
+      }
+      else
       {
-        List<String> vals = map.get(key);
-        if (vals == null)
-        {
-          vals = new ArrayList<String>();
-          map.put(key, vals);
-        }
         for (String val : values.split(valuesDelimiter))
         {
           vals.add(val);
         }
       }
     }
+
     return map;
   }
 
@@ -357,8 +389,7 @@ public abstract class GffHelperBase implements GffHelperI
       int end = Integer.parseInt(gff[END_COL]);
 
       /*
-       * default 'score' is 0 rather than Float.NaN as the latter currently
-       * disables the 'graduated colour => colour by label' option
+       * default 'score' is 0 rather than Float.NaN - see JAL-2554
        */
       float score = 0f;
       try
@@ -379,22 +410,32 @@ public abstract class GffHelperBase implements GffHelperI
       if (attributes != null)
       {
         /*
-         * save 'raw' column 9 to allow roundtrip output as input
-         */
-        sf.setAttributes(gff[ATTRIBUTES_COL]);
-
-        /*
          * Add attributes in column 9 to the sequence feature's 
-         * 'otherData' table; use Note as a best proxy for description
+         * 'otherData' table; use Note as a best proxy for description;
+         * decode any encoded comma, equals, semi-colon as per GFF3 spec
          */
         for (Entry<String, List<String>> attr : attributes.entrySet())
         {
-          String values = StringUtils.listToDelimitedString(attr.getValue(),
-                  ",");
-          sf.setValue(attr.getKey(), values);
-          if (NOTE.equals(attr.getKey()))
+          String key = attr.getKey();
+          List<String> values = attr.getValue();
+          if (values.size() == 1 && values.get(0).contains(EQUALS))
+          {
+            /*
+             * 'value' is actually nested subattributes as x=a,y=b,z=c
+             */
+            Map<String, String> valueMap = parseAttributeMap(values.get(0));
+            sf.setValue(key, valueMap);
+          }
+          else
           {
-            sf.setDescription(values);
+            String csvValues = StringUtils.listToDelimitedString(values,
+                    COMMA);
+            csvValues = StringUtils.urlDecode(csvValues, GFF_ENCODABLE);
+            sf.setValue(key, csvValues);
+            if (NOTE.equals(key))
+            {
+              sf.setDescription(csvValues);
+            }
           }
         }
       }
@@ -408,12 +449,102 @@ public abstract class GffHelperBase implements GffHelperI
   }
 
   /**
-   * Returns the character used to separate attributes names from values in GFF
-   * column 9. This is space for GFF2, '=' for GFF3.
+   * Parses a (GFF3 format) list of comma-separated key=value pairs into a Map
+   * of {@code key,
+   * value} <br>
+   * An input string like {@code a=b,c,d=e,f=g,h} is parsed to
+   * 
+   * <pre>
+   * a = "b,c"
+   * d = "e"
+   * f = "g,h"
+   * </pre>
+   * 
+   * @param s
    * 
    * @return
    */
-  protected abstract char getNameValueSeparator();
+  protected static Map<String, String> parseAttributeMap(String s)
+  {
+    Map<String, String> map = new HashMap<>();
+    String[] fields = s.split(EQUALS);
+
+    /*
+     * format validation
+     */
+    boolean valid = true;
+    if (fields.length < 2)
+    {
+      /*
+       * need at least A=B here
+       */
+      valid = false;
+    }
+    else if (fields[0].isEmpty() || fields[0].contains(COMMA))
+    {
+      /*
+       * A,B=C is not a valid start, nor is =C
+       */
+      valid = false;
+    }
+    else
+    {
+      for (int i = 1; i < fields.length - 1; i++)
+      {
+        if (fields[i].isEmpty() || !fields[i].contains(COMMA))
+        {
+          /*
+           * intermediate tokens must include value,name
+           */
+          valid = false;
+        }
+      }
+    }
+
+    if (!valid)
+    {
+      System.err.println(INVALID_GFF_ATTRIBUTE_FORMAT + s);
+      return map;
+    }
+
+    int i = 0;
+    while (i < fields.length - 1)
+    {
+      boolean lastPair = i == fields.length - 2;
+      String before = fields[i];
+      String after = fields[i + 1];
+
+      /*
+       * if 'key' looks like a,b,c then the last token is the
+       * key
+       */
+      String theKey = before.contains(COMMA)
+              ? before.substring(before.lastIndexOf(COMMA) + 1)
+              : before;
+
+      theKey = theKey.trim();
+      if (theKey.isEmpty())
+      {
+        System.err.println(INVALID_GFF_ATTRIBUTE_FORMAT + s);
+        map.clear();
+        return map;
+      }
+
+      /*
+       * if 'value' looks like a,b,c then all but the last token is the value,
+       * unless this is the last field (no more = to follow), in which case
+       * all of it makes up the value
+       */
+      String theValue = after.contains(COMMA) && !lastPair
+              ? after.substring(0, after.lastIndexOf(COMMA))
+              : after;
+      map.put(StringUtils.urlDecode(theKey, GFF_ENCODABLE),
+              StringUtils.urlDecode(theValue, GFF_ENCODABLE));
+      i += 1;
+    }
+
+    return map;
+  }
 
   /**
    * Returns any existing mapping held on the alignment between the given
index 7fbcf5c..387ee60 100644 (file)
@@ -35,6 +35,12 @@ import java.util.List;
  */
 public interface GffHelperI
 {
+  /*
+   * GFF3 spec requires comma, equals, semi-colon, tab, percent characters to be
+   * encoded as %2C, %3D, %3B, %09, %25 respectively within data values
+   * see https://github.com/The-Sequence-Ontology/Specifications/blob/master/gff3.md
+   */
+  final String GFF_ENCODABLE = ",=;\t%";
 
   final String RENAME_TOKEN = "$RENAME_TO$";
 
index ac707d8..cbdd66c 100644 (file)
@@ -19,11 +19,10 @@ import jalview.io.gff.SequenceOntologyI;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
+import jalview.util.StringUtils;
 
 import java.io.File;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -57,17 +56,7 @@ import htsjdk.variant.vcf.VCFInfoHeaderLine;
  */
 public class VCFLoader
 {
-  private static final String ENCODED_COMMA = "%2C";
-
-  private static final String ENCODED_PERCENT = "%25";
-
-  private static final String ENCODED_EQUALS = "%3D";
-
-  private static final String ENCODED_SEMICOLON = "%3B";
-
-  private static final String ENCODED_COLON = "%3A";
-
-  private static final String UTF_8 = "UTF-8";
+  private static final String VCF_ENCODABLE = ":;=%,";
 
   /*
    * Jalview feature attributes for VCF fixed column data
@@ -1336,42 +1325,17 @@ public class VCFLoader
       String value = getAttributeValue(variant, key, index);
       if (value != null && isValid(variant, key, value))
       {
-        value = decodeSpecialCharacters(value);
+        /*
+         * decode colon, semicolon, equals sign, percent sign, comma (only)
+         * as required by the VCF specification (para 1.2)
+         */
+        value = StringUtils.urlDecode(value, VCF_ENCODABLE);
         addFeatureAttribute(sf, key, value);
       }
     }
   }
 
   /**
-   * Decodes colon, semicolon, equals sign, percent sign, comma to their decoded
-   * form. The VCF specification (para 1.2) requires these to be encoded where not
-   * used with their special meaning in the VCF syntax. Note that general URL
-   * decoding should not be applied, since this would incorrectly decode (for
-   * example) a '+' sign.
-   * 
-   * @param value
-   * @return
-   */
-  protected static String decodeSpecialCharacters(String value)
-  {
-    /*
-     * avoid regex compilation if it is not needed!
-     */
-    if (!value.contains(ENCODED_COLON) && !value.contains(ENCODED_SEMICOLON)
-            && !value.contains(ENCODED_EQUALS)
-            && !value.contains(ENCODED_PERCENT)
-            && !value.contains(ENCODED_COMMA))
-    {
-      return value;
-    }
-
-    value = value.replace(ENCODED_COLON, ":")
-            .replace(ENCODED_SEMICOLON, ";").replace(ENCODED_EQUALS, "=")
-            .replace(ENCODED_PERCENT, "%").replace(ENCODED_COMMA, ",");
-    return value;
-  }
-
-  /**
    * Answers true for '.', null, or an empty value, or if the INFO type is String.
    * If the INFO type is Integer or Float, answers false if the value is not in
    * valid format.
@@ -1489,12 +1453,7 @@ public class VCFLoader
                * VCF spec requires encoding of special characters e.g. '='
                * so decode them here before storing
                */
-              try
-              {
-                field = URLDecoder.decode(field, UTF_8);
-              } catch (UnsupportedEncodingException e)
-              {
-              }
+              field = StringUtils.urlDecode(field, VCF_ENCODABLE);
               csqValues.put(id, field);
             }
           }
index 2d8a4a6..ca0423b 100644 (file)
@@ -3336,8 +3336,10 @@ public class Jalview2XML
                   || tmpSeq.getEnd() != jseq.getEnd())
           {
             System.err.println(
-                    "Warning JAL-2154 regression: updating start/end for sequence "
-                            + tmpSeq.toString() + " to " + jseq);
+                    String.format("Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d",
+                            tmpSeq.getName(), tmpSeq.getStart(),
+                            tmpSeq.getEnd(), jseq.getStart(),
+                            jseq.getEnd()));
           }
         }
         else
index 2e8ace8..1f114a8 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.util;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.regex.Pattern;
@@ -29,8 +31,16 @@ public class StringUtils
   private static final Pattern DELIMITERS_PATTERN = Pattern
           .compile(".*='[^']*(?!')");
 
+  private static final char PERCENT = '%';
+
   private static final boolean DEBUG = false;
 
+  /*
+   * URL encoded characters, indexed by char value
+   * e.g. urlEncodings['='] = urlEncodings[61] = "%3D"
+   */
+  private static String[] urlEncodings = new String[255];
+
   /**
    * Returns a new character array, after inserting characters into the given
    * character array.
@@ -146,7 +156,7 @@ public class StringUtils
     {
       return null;
     }
-    List<String> jv = new ArrayList<String>();
+    List<String> jv = new ArrayList<>();
     int cp = 0, pos, escape;
     boolean wasescaped = false, wasquoted = false;
     String lstitem = null;
@@ -444,4 +454,118 @@ public class StringUtils
     }
     return text;
   }
+
+  /**
+   * Answers the input string with any occurrences of the 'encodeable' characters
+   * replaced by their URL encoding
+   * 
+   * @param s
+   * @param encodable
+   * @return
+   */
+  public static String urlEncode(String s, String encodable)
+  {
+    if (s == null || s.isEmpty())
+    {
+      return s;
+    }
+
+    /*
+     * do % encoding first, as otherwise it may double-encode!
+     */
+    if (encodable.indexOf(PERCENT) != -1)
+    {
+      s = urlEncode(s, PERCENT);
+    }
+
+    for (char c : encodable.toCharArray())
+    {
+      if (c != PERCENT)
+      {
+        s = urlEncode(s, c);
+      }
+    }
+    return s;
+  }
+
+  /**
+   * Answers the input string with any occurrences of {@code c} replaced with
+   * their url encoding. Answers the input string if it is unchanged.
+   * 
+   * @param s
+   * @param c
+   * @return
+   */
+  static String urlEncode(String s, char c)
+  {
+    String decoded = String.valueOf(c);
+    if (s.indexOf(decoded) != -1)
+    {
+      String encoded = getUrlEncoding(c);
+      if (!encoded.equals(decoded))
+      {
+        s = s.replace(decoded, encoded);
+      }
+    }
+    return s;
+  }
+
+  /**
+   * Answers the input string with any occurrences of the specified (unencoded)
+   * characters replaced by their URL decoding.
+   * <p>
+   * Example: {@code urlDecode("a%3Db%3Bc", "-;=,")} should answer
+   * {@code "a=b;c"}.
+   * 
+   * @param s
+   * @param encodable
+   * @return
+   */
+  public static String urlDecode(String s, String encodable)
+  {
+    if (s == null || s.isEmpty())
+    {
+      return s;
+    }
+
+    for (char c : encodable.toCharArray())
+    {
+      String encoded = getUrlEncoding(c);
+      if (s.indexOf(encoded) != -1)
+      {
+        String decoded = String.valueOf(c);
+        s = s.replace(encoded, decoded);
+      }
+    }
+    return s;
+  }
+
+  /**
+   * Does a lazy lookup of the url encoding of the given character, saving the
+   * value for repeat lookups
+   * 
+   * @param c
+   * @return
+   */
+  private static String getUrlEncoding(char c)
+  {
+    if (c < 0 || c >= urlEncodings.length)
+    {
+      return String.valueOf(c);
+    }
+
+    String enc = urlEncodings[c];
+    if (enc == null)
+    {
+      try
+      {
+        enc = urlEncodings[c] = URLEncoder.encode(String.valueOf(c),
+                "UTF-8");
+      } catch (UnsupportedEncodingException e)
+      {
+        enc = urlEncodings[c] = String.valueOf(c);
+      }
+    }
+    return enc;
+  }
 }
index e114ea9..a420d9f 100644 (file)
@@ -52,7 +52,6 @@ import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.regex.Pattern;
 
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
@@ -68,8 +67,6 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy
    */
   private static final String EMBL_NOT_FOUND_REPLY = "ERROR 12 No entries found.";
 
-  private static final Pattern SPACE_PATTERN = Pattern.compile(" ");
-
   public EmblXmlSource()
   {
     super();
@@ -703,19 +700,10 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy
     SequenceFeature sf = new SequenceFeature(type, desc, begin, end, group);
     if (!vals.isEmpty())
     {
-      StringBuilder sb = new StringBuilder();
-      boolean first = true;
       for (Entry<String, String> val : vals.entrySet())
       {
-        if (!first)
-        {
-          sb.append(";");
-        }
-        sb.append(val.getKey()).append("=").append(val.getValue());
-        first = false;
         sf.setValue(val.getKey(), val.getValue());
       }
-      sf.setAttributes(sb.toString());
     }
     return sf;
   }
index 17e92c8..e17b4a6 100644 (file)
 package jalview.ext.ensembl;
 
 import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertSame;
 
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
-import jalview.datamodel.features.SequenceFeatures;
 import jalview.gui.JvOptionPane;
 import jalview.io.DataSourceType;
 import jalview.io.FastaFile;
@@ -34,8 +32,6 @@ import jalview.io.gff.SequenceOntologyFactory;
 import jalview.io.gff.SequenceOntologyLite;
 
 import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
 
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
@@ -223,7 +219,6 @@ public class EnsemblSeqProxyTest
     SequenceFeature sf = new SequenceFeature("sequence_variant", alleles,
             1, 2, 0f, null);
     sf.setValue("alleles", alleles);
-    sf.setAttributes("x=y,z;alleles=" + alleles + ";a=b,c");
 
     EnsemblSeqProxy.reverseComplementAlleles(sf);
     String revcomp = "G,C,GTA-,HGMD_MUTATION,gtc";
@@ -231,7 +226,5 @@ public class EnsemblSeqProxyTest
     assertEquals(revcomp, sf.getDescription());
     // verify alleles attribute is updated with reverse complement
     assertEquals(revcomp, sf.getValue("alleles"));
-    // verify attributes string is updated with reverse complement
-    assertEquals("x=y,z;alleles=" + revcomp + ";a=b,c", sf.getAttributes());
   }
 }
index cd0b594..a03819d 100644 (file)
@@ -570,7 +570,12 @@ public class SeqPanelTest
       evt = new MouseEvent(testee, MouseEvent.MOUSE_MOVED, 0L, 0, x, y, 0, 0, 0,
               false, 0);
       pos = testee.findMousePosition(evt);
-      assertEquals(pos.seqIndex, alignmentHeight - 1);
+      SeqCanvas sc = testee.seqCanvas;
+      assertEquals(pos.seqIndex, alignmentHeight - 1,
+              String.format("%s n=%d y=%d %d, %d, %d, %d",
+                      annotationRows[n].label, n, y, sc.getWidth(),
+                      sc.getHeight(), sc.wrappedRepeatHeightPx,
+                      sc.wrappedSpaceAboveAlignment));
       assertEquals(pos.annotationIndex, n);
     }
   
index 53a0acb..ae5ed25 100644 (file)
  */
 package jalview.io;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
 import jalview.analysis.CrossRef;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.AlignedCodonFrame;
@@ -60,6 +64,51 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
   }
 
+  @Test(groups = { "Functional" }, enabled = true)
+  public void openCrossrefsForEnsemblTwice()
+  {
+    AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
+            "examples/testdata/CantShowEnsemblCrossrefsTwice.jvp",
+            DataSourceType.FILE);
+    assertNotNull(af, "Couldn't load test's project.");
+    AlignmentI origAlig = af.getViewport().getAlignment();
+    List<String> source = new CrossRef(origAlig.getSequencesArray(),
+            origAlig.getDataset()).findXrefSourcesForSequences(true);
+    assertEquals(source.size(), 1, "Expected just one crossref to show.");
+    List<AlignmentViewPanel> views;
+    {
+      // try to show once - in a code block so handler is forgotten about
+      CrossRefAction xref1 = CrossRefAction.getHandlerFor(
+              origAlig.getSequencesArray(), true, source.get(0), af);
+      try
+      {
+        xref1.run();
+        views = (List<AlignmentViewPanel>) PA.getValue(xref1, "xrefViews");
+        assertTrue(views.size() > 0,
+                "Couldn't get cross ref on first attempt (SERIOUS FAIL).");
+      } catch (Exception ex)
+      {
+        Assert.fail("Unexpected Exception for first xref action", ex);
+      }
+    }
+
+    views = null;
+    // now just try it again
+    CrossRefAction xref2 = CrossRefAction.getHandlerFor(
+            origAlig.getSequencesArray(), true, source.get(0), af);
+    try
+    {
+      xref2.run();
+      views = (List<AlignmentViewPanel>) PA.getValue(xref2, "xrefViews");
+      assertTrue(views.size() > 0,
+              "Couldn't get cross ref on second attempt (SERIOUS FAIL).");
+    } catch (Exception ex)
+    {
+      Assert.fail("Unexpected Exception for second xref action", ex);
+    }
+    // TODO : check that both views contain the same data
+  }
+
   @DataProvider(name = "initialAccessions")
   static Object[][] getAccessions()
   {
index 090de6f..298ae6b 100644 (file)
@@ -268,10 +268,12 @@ public class FeaturesFileTest
     AlignFrame af = new AlignFrame(al, 500, 500);
     Map<String, FeatureColourI> colours = af.getFeatureRenderer()
             .getFeatureColours();
-    // GFF3 uses '=' separator for name/value pairs in colum 9
+    // GFF3 uses '=' separator for name/value pairs in column 9
+    // comma (%2C) equals (%3D) or semi-colon (%3B) should be url-escaped in values
     String gffData = "##gff-version 3\n"
             + "FER_CAPAA\tuniprot\tMETAL\t39\t39\t0.0\t.\t.\t"
-            + "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465\n"
+            + "Note=Iron-sulfur (2Fe-2S);Note=another note,and another;evidence=ECO%3B0000255%2CPROSITE%3DProRule:PRU00465;"
+            + "CSQ=AF=21,POLYPHEN=benign,possibly_damaging,clin_sig=Benign%3Dgood\n"
             + "FER1_SOLLC\tuniprot\tPfam\t55\t130\t3.0\t.\t.\tID=$23";
     FeaturesFile featuresFile = new FeaturesFile(gffData,
             DataSourceType.PASTE);
@@ -284,14 +286,25 @@ public class FeaturesFileTest
     assertEquals(1, sfs.size());
     SequenceFeature sf = sfs.get(0);
     // description parsed from Note attribute
-    assertEquals("Iron-sulfur (2Fe-2S),another note", sf.description);
+    assertEquals("Iron-sulfur (2Fe-2S),another note,and another",
+            sf.description);
     assertEquals(39, sf.begin);
     assertEquals(39, sf.end);
     assertEquals("uniprot", sf.featureGroup);
     assertEquals("METAL", sf.type);
-    assertEquals(
-            "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465",
-            sf.getValue("ATTRIBUTES"));
+    assertEquals(5, sf.otherDetails.size());
+    assertEquals("ECO;0000255,PROSITE=ProRule:PRU00465", // url decoded
+            sf.getValue("evidence"));
+    assertEquals("Iron-sulfur (2Fe-2S),another note,and another",
+            sf.getValue("Note"));
+    assertEquals("21", sf.getValueAsString("CSQ", "AF"));
+    assertEquals("benign,possibly_damaging",
+            sf.getValueAsString("CSQ", "POLYPHEN"));
+    assertEquals("Benign=good", sf.getValueAsString("CSQ", "clin_sig")); // url decoded
+    // todo change STRAND and !Phase into fields of SequenceFeature instead
+    assertEquals(".", sf.otherDetails.get("STRAND"));
+    assertEquals(0, sf.getStrand());
+    assertEquals(".", sf.getPhase());
 
     // verify feature on FER1_SOLLC1
     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
@@ -593,9 +606,14 @@ public class FeaturesFileTest
                             "s3dm"));
     SequenceFeature sf = new SequenceFeature("Pfam", "", 20, 20, 0f,
             "Uniprot");
-    sf.setAttributes("x=y;black=white");
     sf.setStrand("+");
     sf.setPhase("2");
+    sf.setValue("x", "y");
+    sf.setValue("black", "white");
+    Map<String, String> csq = new HashMap<>();
+    csq.put("SIFT", "benign,mostly benign,cloudy, with meatballs");
+    csq.put("consequence", "missense_variant");
+    sf.setValue("CSQ", csq);
     al.getSequenceAt(1).addSequenceFeature(sf);
 
     /*
@@ -660,7 +678,11 @@ public class FeaturesFileTest
     // Pfam feature columns include strand(+), phase(2), attributes
     expected = gffHeader
             + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
-            + "FER_CAPAN\tUniprot\tPfam\t20\t20\t0.0\t+\t2\tx=y;black=white\n"
+            // CSQ output as CSQ=att1=value1,att2=value2
+            // note all commas are encoded here which is wrong - it should be
+            // SIFT=benign,mostly benign,cloudy%2C with meatballs
+            + "FER_CAPAN\tUniprot\tPfam\t20\t20\t0.0\t+\t2\tx=y;black=white;"
+            + "CSQ=SIFT=benign%2Cmostly benign%2Ccloudy%2C with meatballs,consequence=missense_variant\n"
             + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n";
     assertEquals(expected, exported);
   }
@@ -772,8 +794,8 @@ public class FeaturesFileTest
     String exported = featuresFile.printGffFormat(al.getSequencesArray(),
             fr, false, false);
     String expected = gffHeader
-            + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
-            + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\n";
+            + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\tclin_sig=Likely Pathogenic;AF=24\n"
+            + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\tclin_sig=Benign;AF=46\n";
     assertEquals(expected, exported);
 
     /*
@@ -786,7 +808,8 @@ public class FeaturesFileTest
     fr.setColour("METAL", fc);
     exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
             false, false);
-    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n";
+    expected = gffHeader
+            + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\tclin_sig=Likely Pathogenic;AF=24\n";
     assertEquals(expected, exported);
 
     /*
@@ -795,8 +818,9 @@ public class FeaturesFileTest
     fc.setAboveThreshold(false);
     exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
             false, false);
-    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
-            + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\n";
+    expected = gffHeader
+            + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\tclin_sig=Likely Pathogenic;AF=24\n"
+            + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\tclin_sig=Benign;AF=46\n";
     assertEquals(expected, exported);
 
     /*
@@ -808,7 +832,8 @@ public class FeaturesFileTest
     fr.setFeatureFilter("METAL", filter);
     exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
             false, false);
-    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\n";
+    expected = gffHeader
+            + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\tclin_sig=Benign;AF=46\n";
     assertEquals(expected, exported);
   }
 
index 7fb716f..a23518d 100644 (file)
  */
 package jalview.io.gff;
 
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 import jalview.gui.JvOptionPane;
 
@@ -59,25 +60,38 @@ public class GffHelperBaseTest
 
     Map<String, List<String>> map = GffHelperBase.parseNameValuePairs(
             "hello world", ";", ' ', ", ");
-    assertEquals(1, map.size());
-    assertEquals(1, map.get("hello").size());
-    assertEquals("world", map.get("hello").get(0));
+    assertEquals(map.size(), 1);
+    assertEquals(map.get("hello").size(), 1);
+    assertEquals(map.get("hello").get(0), "world");
 
     map = GffHelperBase
             .parseNameValuePairs(
-                    "Method= manual curation ;nothing; Notes=F2 S ; Notes=Metal,Shiny; Type=",
+                    "Method= manual curation ;nothing; Notes=F2 S ; Notes=Metal,Shiny%2Csmooth; Type=",
                     ";", '=', ",");
 
     // Type is ignored as no value was supplied
-    assertEquals(2, map.size());
+    assertEquals(map.size(), 2);
 
-    assertEquals(1, map.get("Method").size());
-    assertEquals("manual curation", map.get("Method").get(0)); // trimmed
+    assertEquals(map.get("Method").size(), 1);
+    assertEquals(map.get("Method").get(0), "manual curation"); // trimmed
 
-    assertEquals(3, map.get("Notes").size());
-    assertEquals("F2 S", map.get("Notes").get(0));
-    assertEquals("Metal", map.get("Notes").get(1));
-    assertEquals("Shiny", map.get("Notes").get(2));
+    assertEquals(map.get("Notes").size(), 3);
+    assertEquals(map.get("Notes").get(0), "F2 S");
+    assertEquals(map.get("Notes").get(1), "Metal");
+    assertEquals(map.get("Notes").get(2), "Shiny%2Csmooth"); // not decoded here
+
+    /*
+     * gff3 style with nested attribute values
+     */
+    String csqValue = "POLYPHEN=possibly_damaging,probably_damaging,SIFT=tolerated%2Cdeleterious";
+    map = GffHelperBase.parseNameValuePairs("hello=world;CSQ=" + csqValue,
+            ";", '=', ",");
+    assertEquals(map.size(), 2); // keys hello, CSQ
+    assertEquals(map.get("hello").size(), 1);
+    assertEquals(map.get("hello").get(0), "world");
+    // CSQ values is read 'raw' here, and parsed further elsewhere
+    assertEquals(map.get("CSQ").size(), 1);
+    assertEquals(map.get("CSQ").get(0), csqValue);
   }
 
   /**
@@ -89,110 +103,164 @@ public class GffHelperBaseTest
     int[] from = { 1, 12 };
     int[] to = { 20, 31 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 1));
-    assertEquals("[1, 12]", Arrays.toString(from)); // unchanged
-    assertEquals("[20, 31]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[1, 12]"); // unchanged
+    assertEquals(Arrays.toString(to), "[20, 31]"); // unchanged
 
     // from too long:
     from = new int[] { 1, 13 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 1));
-    assertEquals("[1, 12]", Arrays.toString(from)); // trimmed
-    assertEquals("[20, 31]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[1, 12]"); // trimmed
+    assertEquals(Arrays.toString(to), "[20, 31]"); // unchanged
 
     // to too long:
     to = new int[] { 20, 33 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 1));
-    assertEquals("[1, 12]", Arrays.toString(from)); // unchanged
-    assertEquals("[20, 31]", Arrays.toString(to)); // trimmed
+    assertEquals(Arrays.toString(from), "[1, 12]"); // unchanged
+    assertEquals(Arrays.toString(to), "[20, 31]"); // trimmed
 
     // from reversed:
     from = new int[] { 12, 1 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 1));
-    assertEquals("[12, 1]", Arrays.toString(from)); // unchanged
-    assertEquals("[20, 31]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[12, 1]"); // unchanged
+    assertEquals(Arrays.toString(to), "[20, 31]"); // unchanged
 
     // to reversed:
     to = new int[] { 31, 20 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 1));
-    assertEquals("[12, 1]", Arrays.toString(from)); // unchanged
-    assertEquals("[31, 20]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[12, 1]"); // unchanged
+    assertEquals(Arrays.toString(to), "[31, 20]"); // unchanged
 
     // from reversed and too long:
     from = new int[] { 14, 1 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 1));
-    assertEquals("[14, 3]", Arrays.toString(from)); // end trimmed
-    assertEquals("[31, 20]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[14, 3]"); // end trimmed
+    assertEquals(Arrays.toString(to), "[31, 20]"); // unchanged
 
     // to reversed and too long:
     to = new int[] { 31, 10 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 1));
-    assertEquals("[14, 3]", Arrays.toString(from)); // unchanged
-    assertEquals("[31, 20]", Arrays.toString(to)); // end trimmed
+    assertEquals(Arrays.toString(from), "[14, 3]"); // unchanged
+    assertEquals(Arrays.toString(to), "[31, 20]"); // end trimmed
 
     // cdna to peptide (matching)
     from = new int[] { 1, 18 };
     to = new int[] { 4, 9 };
     assertTrue(GffHelperBase.trimMapping(from, to, 3, 1));
-    assertEquals("[1, 18]", Arrays.toString(from)); // unchanged
-    assertEquals("[4, 9]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[1, 18]"); // unchanged
+    assertEquals(Arrays.toString(to), "[4, 9]"); // unchanged
 
     // overlong cdna to peptide
     from = new int[] { 1, 20 };
     assertTrue(GffHelperBase.trimMapping(from, to, 3, 1));
-    assertEquals("[1, 18]", Arrays.toString(from)); // end trimmed
-    assertEquals("[4, 9]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[1, 18]"); // end trimmed
+    assertEquals(Arrays.toString(to), "[4, 9]"); // unchanged
 
     // overlong cdna (reversed) to peptide
     from = new int[] { 20, 1 };
     assertTrue(GffHelperBase.trimMapping(from, to, 3, 1));
-    assertEquals("[20, 3]", Arrays.toString(from)); // end trimmed
-    assertEquals("[4, 9]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[20, 3]"); // end trimmed
+    assertEquals(Arrays.toString(to), "[4, 9]"); // unchanged
 
     // overlong cdna (reversed) to peptide (reversed)
     from = new int[] { 20, 1 };
     to = new int[] { 9, 4 };
     assertTrue(GffHelperBase.trimMapping(from, to, 3, 1));
-    assertEquals("[20, 3]", Arrays.toString(from)); // end trimmed
-    assertEquals("[9, 4]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[20, 3]"); // end trimmed
+    assertEquals(Arrays.toString(to), "[9, 4]"); // unchanged
 
     // peptide to cdna (matching)
     from = new int[] { 4, 9 };
     to = new int[] { 1, 18 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 3));
-    assertEquals("[4, 9]", Arrays.toString(from)); // unchanged
-    assertEquals("[1, 18]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[4, 9]"); // unchanged
+    assertEquals(Arrays.toString(to), "[1, 18]"); // unchanged
 
     // peptide to overlong cdna
     to = new int[] { 1, 20 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 3));
-    assertEquals("[4, 9]", Arrays.toString(from)); // unchanged
-    assertEquals("[1, 18]", Arrays.toString(to)); // end trimmed
+    assertEquals(Arrays.toString(from), "[4, 9]"); // unchanged
+    assertEquals(Arrays.toString(to), "[1, 18]"); // end trimmed
 
     // peptide to overlong cdna (reversed)
     to = new int[] { 20, 1 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 3));
-    assertEquals("[4, 9]", Arrays.toString(from)); // unchanged
-    assertEquals("[20, 3]", Arrays.toString(to)); // end trimmed
+    assertEquals(Arrays.toString(from), "[4, 9]"); // unchanged
+    assertEquals(Arrays.toString(to), "[20, 3]"); // end trimmed
 
     // peptide (reversed) to overlong cdna (reversed)
     from = new int[] { 9, 4 };
     to = new int[] { 20, 1 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 3));
-    assertEquals("[9, 4]", Arrays.toString(from)); // unchanged
-    assertEquals("[20, 3]", Arrays.toString(to)); // end trimmed
+    assertEquals(Arrays.toString(from), "[9, 4]"); // unchanged
+    assertEquals(Arrays.toString(to), "[20, 3]"); // end trimmed
 
     // overlong peptide to word-length cdna
     from = new int[] { 4, 10 };
     to = new int[] { 1, 18 };
     assertTrue(GffHelperBase.trimMapping(from, to, 1, 3));
-    assertEquals("[4, 9]", Arrays.toString(from)); // end trimmed
-    assertEquals("[1, 18]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[4, 9]"); // end trimmed
+    assertEquals(Arrays.toString(to), "[1, 18]"); // unchanged
 
     // overlong peptide to non-word-length cdna
     from = new int[] { 4, 10 };
     to = new int[] { 1, 19 };
     assertFalse(GffHelperBase.trimMapping(from, to, 1, 3));
-    assertEquals("[4, 10]", Arrays.toString(from)); // unchanged
-    assertEquals("[1, 19]", Arrays.toString(to)); // unchanged
+    assertEquals(Arrays.toString(from), "[4, 10]"); // unchanged
+    assertEquals(Arrays.toString(to), "[1, 19]"); // unchanged
+  }
+
+  @Test(groups = { "Functional" })
+  public void testParseAttributeMap()
+  {
+    Map<String, String> map = GffHelperBase
+            .parseAttributeMap("A=B,C%2C%3D%3B%09%25D,X=Y");
+    assertEquals(map.size(), 2);
+    // value of A is everything up to and excluding ,X=
+    assertEquals(map.get("A"), "B,C,=;\t%D");
+    assertEquals(map.get("X"), "Y");
+
+    /*
+     * malformed cases should result in an empty map
+     */
+    map = GffHelperBase.parseAttributeMap("=B=Y");
+    assertTrue(map.isEmpty());
+    // first token should be an attribute name only, no commas
+    map = GffHelperBase.parseAttributeMap("A,B=C");
+    assertTrue(map.isEmpty());
+    // intermediate tokens need at least one comma (value,name=)
+    map = GffHelperBase.parseAttributeMap("A=B=C");
+    assertTrue(map.isEmpty());
+    // last token may have a comma or not
+    map = GffHelperBase.parseAttributeMap("A=B");
+    assertEquals(map.get("A"), "B");
+    map = GffHelperBase.parseAttributeMap("A=B,C");
+    assertEquals(map.get("A"), "B,C");
+    map = GffHelperBase.parseAttributeMap("A");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap("A=");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap("A==C");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap("=A");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap("=");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap(",");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap(" ");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap("");
+    assertTrue(map.isEmpty());
+    map = GffHelperBase.parseAttributeMap("A=B, =C");
+    assertTrue(map.isEmpty());
 
+    try
+    {
+      GffHelperBase.parseAttributeMap(null);
+      fail("expected exception");
+    } catch (NullPointerException e)
+    {
+      // expected
+    }
   }
 }
index 87cf727..b206f8c 100644 (file)
@@ -3,7 +3,6 @@ package jalview.io.vcf;
 import static jalview.io.gff.SequenceOntologyI.SEQUENCE_VARIANT;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 
 import jalview.bin.Cache;
@@ -543,7 +542,7 @@ public class VCFLoaderTest
     assertEquals(sf.getValue("alleles"), "C,T");
     map = (Map) sf.getValue("CSQ");
     assertEquals(map.size(), 9);
-    assertEquals(map.get("PolyPhen"), "Bad++"); // %3B%3B decoded
+    assertEquals(map.get("PolyPhen"), "Bad;;"); // %3B%3B decoded
 
     sf = geneFeatures.get(2);
     assertEquals(sf.getBegin(), 9);
@@ -762,16 +761,4 @@ public class VCFLoaderTest
     assertEquals(sf.getEnd(), 15);
     assertEquals(sf.getDescription(), "T,C");
   }
-
-  @Test(groups = "Functional")
-  public void testDecodeSpecialCharacters() throws IOException
-  {
-    String encoded = "hello world";
-    String decoded = VCFLoader.decodeSpecialCharacters(encoded);
-    assertSame(encoded, decoded); // no change needed
-
-    encoded = "ab%3Acd%3Bef%3Dgh%25ij%2Ckl%3A";
-    decoded = VCFLoader.decodeSpecialCharacters(encoded);
-    assertEquals(decoded, "ab:cd;ef=gh%ij,kl:");
-  }
 }
\ No newline at end of file
index 8a16a90..1956cbc 100644 (file)
@@ -7,7 +7,7 @@
 ##reference=/Homo_sapiens/GRCh38
 #CHROM POS     ID      REF     ALT     QUAL    FILTER  INFO
 5      45051610        .       C       A       81.96   RF;AC0  AC=1;AF=0.1;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=A|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,A|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad
-5      45051614        .       C       T       1666.64 RF      AC=1;AF=0.2;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad%2B%2B
+5      45051614        .       C       T       1666.64 RF      AC=1;AF=0.2;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad%3B%3B
 5      45051618        .       CGG     C       41.94   AC0     AC=1;AF=0.3;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=C|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,C|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,CSQ=CGT|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,CGT|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad
 5      45051622        .       C       G,T     224.23  RF;AC0  AC=1,2;AF=0.4,0.5;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=G|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,G|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,T|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,T|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad
 5      45051626        .       A       AC,G    433.35  RF;AC0  AC=3,4;AF=0.6,0.7;AN=0;AF_Female=2;AB_MEDIAN=6.00000e-01;CSQ=G|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,G|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad,AC|missense_variant|MODIFIER|WASH7P|gene3|Transcript|transcript3|rna|Benign,AC|downstream_gene_variant|MODIFIER|WASH7P|gene3|Transcript|transcript4|mrna|Bad
index 084219a..37506c0 100644 (file)
@@ -145,7 +145,7 @@ public class StringUtilsTest
   public void testListToDelimitedString()
   {
     assertEquals("", StringUtils.listToDelimitedString(null, ";"));
-    List<String> list = new ArrayList<String>();
+    List<String> list = new ArrayList<>();
     assertEquals("", StringUtils.listToDelimitedString(list, ";"));
     list.add("now");
     assertEquals("now", StringUtils.listToDelimitedString(list, ";"));
@@ -250,4 +250,70 @@ public class StringUtilsTest
     assertEquals("kdHydro &lt; 12.53",
             StringUtils.stripHtmlTags("kdHydro < 12.53"));
   }
+
+  @Test(groups = { "Functional" })
+  public void testUrlEncode()
+  {
+    // degenerate cases
+    assertNull(StringUtils.urlEncode(null, ";,"));
+    assertEquals("", StringUtils.urlEncode("", ""));
+    assertEquals("", StringUtils.urlEncode("", ";,"));
+
+    // sanity checks, see
+    // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters
+    assertEquals("+", StringUtils.urlEncode(" ", " "));
+    assertEquals("%25", StringUtils.urlEncode("%", "%"));
+    assertEquals(".", StringUtils.urlEncode(".", ".")); // note . is not encoded
+    assertEquals("%3A", StringUtils.urlEncode(":", ":"));
+    assertEquals("%3B", StringUtils.urlEncode(";", ";"));
+    assertEquals("%3D", StringUtils.urlEncode("=", "="));
+    assertEquals("%2C", StringUtils.urlEncode(",", ","));
+
+    // check % does not get recursively encoded!
+    assertEquals("a%25b%3Dc%3Bd%3Ae%2C%2C",
+            StringUtils.urlEncode("a%b=c;d:e,,", "=,;:%"));
+
+    // = not in the list for encoding
+    assertEquals("a=b", StringUtils.urlEncode("a=b", ";,"));
+
+    // encode = (as %3B) and ; (as %3D)
+    assertEquals("a%3Db.c%3B", StringUtils.urlEncode("a=b.c;", ";=,"));
+
+    // . and space not in the list for encoding
+    assertEquals("a%3Db.c d", StringUtils.urlEncode("a=b.c d", ";=,"));
+
+    // encode space also (as +)
+    assertEquals("a%3Db.c+d", StringUtils.urlEncode("a=b.c d", ";=, "));
+
+    // . does not get encoded even if requested - behaviour of URLEncoder
+    assertEquals("a%3Db.c+d.e%3Df",
+            StringUtils.urlEncode("a=b.c d.e=f", ";=,. "));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testUrlDecode()
+  {
+    // degenerate cases
+    assertNull(StringUtils.urlDecode(null, ";,"));
+    assertEquals("", StringUtils.urlDecode("", ""));
+    assertEquals("", StringUtils.urlDecode("", ";,"));
+
+    // = not in the list for encoding
+    assertEquals("a%3Db", StringUtils.urlDecode("a%3Db", ";,"));
+
+    // decode = and ; but not .
+    assertEquals("a=b%3Ec; d",
+            StringUtils.urlDecode("a%3Db%3Ec; d", ";=,"));
+
+    // space not in the list for decoding
+    assertEquals("a=b;c+d", StringUtils.urlDecode("a%3Db%3Bc+d", ";=,"));
+
+    // decode space also; %3E is not decoded to .
+    assertEquals("a=b%3Ec d=,",
+            StringUtils.urlDecode("a%3Db%3Ec+d%3D%2C", ";=, "));
+
+    // decode encoded % (%25)
+    assertEquals("a,=;\t%z",
+            StringUtils.urlDecode("a%2C%3D%3B%09%25z", ";=,\t%"));
+  }
 }