JAL-3121 round trip GFF attributes including map-valued attributes
[jalview.git] / src / jalview / io / gff / GffHelperBase.java
index 1d4d3ac..d034c8d 100644 (file)
@@ -27,6 +27,7 @@ import jalview.datamodel.MappingType;
 import jalview.datamodel.SequenceDummy;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.io.FeaturesFile;
 import jalview.util.MapList;
 import jalview.util.StringUtils;
 
@@ -286,7 +287,7 @@ public abstract class GffHelperBase implements GffHelperI
           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;
@@ -314,7 +315,7 @@ public abstract class GffHelperBase implements GffHelperI
         List<String> vals = map.get(key);
         if (vals == null)
         {
-          vals = new ArrayList<String>();
+          vals = new ArrayList<>();
           map.put(key, vals);
         }
         for (String val : values.split(valuesDelimiter))
@@ -357,8 +358,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 +379,66 @@ 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> value = attr.getValue();
+          if (key.startsWith(FeaturesFile.MAP_ATTRIBUTE_PREFIX))
+          {
+            /*
+             * e.g. jvmap_CSQ={ALLELE_NUM=1,CDS_position=249,Codons=caG/caT}
+             */
+            String trueKey = key
+                    .substring(FeaturesFile.MAP_ATTRIBUTE_PREFIX.length());
+            if (trueKey.isEmpty() || value.isEmpty()
+                    || !value.get(0).startsWith("{")
+                    || !value.get(value.size() - 1).endsWith("}"))
+            {
+              System.err.println("Malformed GFF data '" + value.toString()
+                      + "' for " + key);
+              continue;
+            }
+            Map<String, String> values = new HashMap<>();
+            for (String entry : value)
+            {
+              if (entry.startsWith("{"))
+              {
+                entry = entry.substring(1);
+              }
+              if (entry.endsWith("}"))
+              {
+                entry = entry.substring(0, entry.length() - 1);
+              }
+              String[] fields = entry.split(",");
+            for (String field : fields)
+            {
+              String[] keyValue = field.split("=");
+              if (keyValue.length == 2)
+              {
+                String theKey = StringUtils.urlDecode(keyValue[0],
+                        GFF_ENCODABLE);
+                String theValue = StringUtils.urlDecode(keyValue[1],
+                        GFF_ENCODABLE);
+                values.put(theKey, theValue);
+              }
+            }
+            }
+            sf.setValue(trueKey, values);
+          }
+          else
           {
-            sf.setDescription(values);
+            String values = StringUtils
+                    .listToDelimitedString(value, ",");
+            values = StringUtils.urlDecode(values, GFF_ENCODABLE);
+            sf.setValue(key, values);
+            if (NOTE.equals(key))
+            {
+              sf.setDescription(values);
+            }
           }
         }
       }