JAL-3949 Complete new abstracted logging framework in jalview.log. Updated log calls...
[jalview.git] / src / jalview / io / vcf / VCFLoader.java
index decff23..b96de68 100644 (file)
@@ -1,25 +1,26 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.io.vcf;
 
-import jalview.analysis.AlignmentUtils;
-import jalview.analysis.Dna;
-import jalview.api.AlignViewControllerGuiI;
-import jalview.bin.Cache;
-import jalview.datamodel.DBRefEntry;
-import jalview.datamodel.GeneLociI;
-import jalview.datamodel.Mapping;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceI;
-import jalview.datamodel.features.FeatureAttributeType;
-import jalview.datamodel.features.FeatureSource;
-import jalview.datamodel.features.FeatureSources;
-import jalview.ext.ensembl.EnsemblMap;
-import jalview.ext.htsjdk.HtsContigDb;
-import jalview.ext.htsjdk.VCFReader;
-import jalview.io.gff.Gff3Helper;
-import jalview.io.gff.SequenceOntologyI;
-import jalview.util.MapList;
-import jalview.util.MappingUtils;
-import jalview.util.MessageManager;
+import java.util.Locale;
 
 import java.io.File;
 import java.io.IOException;
@@ -47,6 +48,26 @@ import htsjdk.variant.vcf.VCFHeaderLine;
 import htsjdk.variant.vcf.VCFHeaderLineCount;
 import htsjdk.variant.vcf.VCFHeaderLineType;
 import htsjdk.variant.vcf.VCFInfoHeaderLine;
+import jalview.analysis.Dna;
+import jalview.api.AlignViewControllerGuiI;
+import jalview.bin.Cache;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.GeneLociI;
+import jalview.datamodel.Mapping;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.FeatureAttributeType;
+import jalview.datamodel.features.FeatureSource;
+import jalview.datamodel.features.FeatureSources;
+import jalview.ext.ensembl.EnsemblMap;
+import jalview.ext.htsjdk.HtsContigDb;
+import jalview.ext.htsjdk.VCFReader;
+import jalview.io.gff.Gff3Helper;
+import jalview.io.gff.SequenceOntologyI;
+import jalview.util.MapList;
+import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
+import jalview.util.StringUtils;
 
 /**
  * A class to read VCF data (using the htsjdk) and add variants as sequence
@@ -56,6 +77,8 @@ import htsjdk.variant.vcf.VCFInfoHeaderLine;
  */
 public class VCFLoader
 {
+  private static final String VCF_ENCODABLE = ":;=%,";
+
   /*
    * Jalview feature attributes for VCF fixed column data
    */
@@ -291,7 +314,7 @@ public class VCFLoader
     VCFHeaderLine headerLine = header.getOtherHeaderLine(VCFHeader.REFERENCE_KEY);
     if (headerLine == null)
     {
-      Cache.log.error("VCF reference header not found");
+      Cache.error("VCF reference header not found");
       return null;
     }
     String ref = headerLine.getValue();
@@ -313,7 +336,7 @@ public class VCFLoader
     }
     else
     {
-      Cache.log.error("VCF reference not found: " + ref);
+      Cache.error("VCF reference not found: " + ref);
     }
 
     return seq;
@@ -406,11 +429,11 @@ public class VCFLoader
   {
     if (reference == null)
     {
-      Cache.log.error("No VCF ##reference found, defaulting to "
+      Cache.error("No VCF ##reference found, defaulting to "
               + DEFAULT_REFERENCE + ":" + DEFAULT_SPECIES);
       reference = DEFAULT_REFERENCE; // default to GRCh37 if not specified
     }
-    reference = reference.toLowerCase();
+    reference = reference.toLowerCase(Locale.ROOT);
 
     /*
      * for a non-human species, or other assembly identifier,
@@ -425,7 +448,7 @@ public class VCFLoader
       String[] tokens = token.split("=");
       if (tokens.length == 2)
       {
-        if (reference.contains(tokens[0].trim().toLowerCase()))
+        if (reference.contains(tokens[0].trim().toLowerCase(Locale.ROOT)))
         {
           vcfAssembly = tokens[1].trim();
           break;
@@ -442,7 +465,7 @@ public class VCFLoader
         String[] tokens = token.split("=");
         if (tokens.length == 2)
         {
-          if (reference.contains(tokens[0].trim().toLowerCase()))
+          if (reference.contains(tokens[0].trim().toLowerCase(Locale.ROOT)))
           {
             vcfSpecies = tokens[1].trim();
             break;
@@ -547,7 +570,7 @@ public class VCFLoader
   {
     for (Pattern p : filters)
     {
-      if (p.matcher(id.toUpperCase()).matches())
+      if (p.matcher(id.toUpperCase(Locale.ROOT)).matches())
       {
         return true;
       }
@@ -641,7 +664,7 @@ public class VCFLoader
     {
       try
       {
-      patterns.add(Pattern.compile(token.toUpperCase()));
+      patterns.add(Pattern.compile(token.toUpperCase(Locale.ROOT)));
       } catch (PatternSyntaxException e)
       {
         System.err.println("Invalid pattern ignored: " + token);
@@ -652,13 +675,12 @@ public class VCFLoader
 
   /**
    * Transfers VCF features to sequences to which this sequence has a mapping.
-   * If the mapping is 3:1, computes peptide variants from nucleotide variants.
    * 
    * @param seq
    */
   protected void transferAddedFeatures(SequenceI seq)
   {
-    DBRefEntry[] dbrefs = seq.getDBRefs();
+    List<DBRefEntry> dbrefs = seq.getDBRefs();
     if (dbrefs == null)
     {
       return;
@@ -678,7 +700,8 @@ public class VCFLoader
         /*
          * dna-to-peptide product mapping
          */
-        AlignmentUtils.computeProteinFeatures(seq, mapTo, map);
+        // JAL-3187 render on the fly instead
+        // AlignmentUtils.computeProteinFeatures(seq, mapTo, map);
       }
       else
       {
@@ -752,7 +775,7 @@ public class VCFLoader
     GeneLociI seqCoords = seq.getGeneLoci();
     if (seqCoords == null)
     {
-      Cache.log.warn(String.format(
+      Cache.warn(String.format(
               "Can't query VCF for %s as chromosome coordinates not known",
               seq.getName()));
       return null;
@@ -767,7 +790,7 @@ public class VCFLoader
     // returned with the Ensembl sequence; todo: support aliases?
     if (!vcfSpecies.equalsIgnoreCase(species))
     {
-      Cache.log.warn("No VCF loaded to " + seq.getName()
+      Cache.warn("No VCF loaded to " + seq.getName()
               + " as species not matched");
       return null;
     }
@@ -797,7 +820,7 @@ public class VCFLoader
               vcfAssembly);
       if (newRange == null)
       {
-        Cache.log.error(
+        Cache.error(
                 String.format("Failed to map %s:%s:%s:%d:%d to %s", species,
                         chromosome, seqRef, range[0], range[1],
                         vcfAssembly));
@@ -874,12 +897,19 @@ public class VCFLoader
           int[] featureRange = map.map.locateInFrom(variant.getStart(),
                   variant.getEnd());
 
+          /*
+           * only take features whose range is fully mappable to sequence positions
+           */
           if (featureRange != null)
           {
             int featureStart = Math.min(featureRange[0], featureRange[1]);
             int featureEnd = Math.max(featureRange[0], featureRange[1]);
-            count += addAlleleFeatures(seq, variant, featureStart,
-                    featureEnd, forwardStrand);
+            if (featureEnd - featureStart == variant.getEnd()
+                    - variant.getStart())
+            {
+              count += addAlleleFeatures(seq, variant, featureStart,
+                      featureEnd, forwardStrand);
+            }
           }
         }
         variants.close();
@@ -889,8 +919,8 @@ public class VCFLoader
          * RuntimeException throwable by htsjdk
          */
         String msg = String.format("Error reading VCF for %s:%d-%d: %s ",
-                map.chromosome, vcfStart, vcfEnd);
-        Cache.log.error(msg);
+                map.chromosome, vcfStart, vcfEnd,e.getLocalizedMessage());
+        Cache.error(msg);
       }
     }
 
@@ -1005,7 +1035,7 @@ public class VCFLoader
      */
     String consequence = getConsequenceForAlleleAndFeature(variant, CSQ_FIELD,
             altAlleleIndex, csqAlleleFieldIndex,
-            csqAlleleNumberFieldIndex, seq.getName().toLowerCase(),
+            csqAlleleNumberFieldIndex, seq.getName().toLowerCase(Locale.ROOT),
             csqFeatureFieldIndex);
 
     /*
@@ -1198,7 +1228,7 @@ public class VCFLoader
       {
         String featureIdentifier = csqFields[featureFieldIndex];
         if (featureIdentifier.length() > 4
-                && seqName.indexOf(featureIdentifier.toLowerCase()) > -1)
+                && seqName.indexOf(featureIdentifier.toLowerCase(Locale.ROOT)) > -1)
         {
           /*
            * feature (transcript) matched - now check for allele match
@@ -1284,14 +1314,6 @@ public class VCFLoader
       }
 
       /*
-       * filter out fields we don't want to capture
-       */
-      if (!vcfFieldsOfInterest.contains(key))
-      {
-        continue;
-      }
-
-      /*
        * we extract values for other data which are allele-specific; 
        * these may be per alternate allele (INFO[key].Number = 'A') 
        * or per allele including reference (INFO[key].Number = 'R') 
@@ -1330,6 +1352,11 @@ public class VCFLoader
       String value = getAttributeValue(variant, key, index);
       if (value != null && isValid(variant, key, 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);
       }
     }
@@ -1355,7 +1382,7 @@ public class VCFLoader
     VCFInfoHeaderLine infoHeader = header.getInfoHeaderLine(infoId);
     if (infoHeader == null)
     {
-      Cache.log.error("Field " + infoId + " has no INFO header");
+      Cache.error("Field " + infoId + " has no INFO header");
       return false;
     }
     VCFHeaderLineType infoType = infoHeader.getType();
@@ -1396,7 +1423,7 @@ public class VCFLoader
     if (!badData.contains(token))
     {
       badData.add(token);
-      Cache.log.error(String.format("Invalid VCF data at %s:%d %s=%s",
+      Cache.error(String.format("Invalid VCF data at %s:%d %s=%s",
               variant.getContig(), variant.getStart(), infoId, value));
     }
   }
@@ -1449,6 +1476,11 @@ public class VCFLoader
             String id = vepFieldsOfInterest.get(i);
             if (id != null)
             {
+              /*
+               * VCF spec requires encoding of special characters e.g. '='
+               * so decode them here before storing
+               */
+              field = StringUtils.urlDecode(field, VCF_ENCODABLE);
               csqValues.put(id, field);
             }
           }