JAL-2835 spike updated with latest
[jalview.git] / src / jalview / datamodel / features / FeatureAttributes.java
index 3dc4f19..7221d62 100644 (file)
@@ -2,9 +2,11 @@ package jalview.datamodel.features;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.TreeMap;
 
 /**
@@ -14,7 +16,45 @@ public class FeatureAttributes
 {
   private static FeatureAttributes instance = new FeatureAttributes();
 
-  private Map<String, Map<String, AttributeData>> attributes;
+  /*
+   * map, by feature type, of a map, by attribute name, of
+   * attribute description and min-max range (if known)
+   */
+  private Map<String, Map<String[], AttributeData>> attributes;
+
+  /*
+   * a case-insensitive comparator so that attributes are ordered e.g.
+   * AC
+   * af
+   * CSQ:AFR_MAF
+   * CSQ:Allele
+   */
+  private Comparator<String[]> comparator = new Comparator<String[]>()
+  {
+    @Override
+    public int compare(String[] o1, String[] o2)
+    {
+      int i = 0;
+      while (i < o1.length || i < o2.length)
+      {
+        if (o2.length <= i)
+        {
+          return o1.length <= i ? 0 : 1;
+        }
+        if (o1.length <= i)
+        {
+          return -1;
+        }
+        int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
+        if (comp != 0)
+        {
+          return comp;
+        }
+        i++;
+      }
+      return 0; // same length and all matched
+    }
+  };
 
   private class AttributeData
   {
@@ -116,17 +156,19 @@ public class FeatureAttributes
   }
 
   /**
-   * Answers the attributes known for the given feature type, in alphabetical
-   * order (not case sensitive), or an empty set if no attributes are known
+   * Answers the attribute names known for the given feature type, in
+   * alphabetical order (not case sensitive), or an empty set if no attributes
+   * are known. An attribute name is typically 'simple' e.g. "AC", but may be
+   * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
    * 
    * @param featureType
    * @return
    */
-  public List<String> getAttributes(String featureType)
+  public List<String[]> getAttributes(String featureType)
   {
     if (!attributes.containsKey(featureType))
     {
-      return Collections.<String> emptyList();
+      return Collections.<String[]> emptyList();
     }
 
     return new ArrayList<>(attributes.get(featureType).keySet());
@@ -156,23 +198,39 @@ public class FeatureAttributes
    * type, and updates the min-max for any numeric value
    * 
    * @param featureType
-   * @param attName
    * @param description
    * @param value
+   * @param attName
    */
-  public void addAttribute(String featureType, String attName,
-          String description, String value)
+  public void addAttribute(String featureType, String description,
+          Object value, String... attName)
   {
     if (featureType == null || attName == null)
     {
       return;
     }
 
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    /*
+     * if attribute value is a map, drill down one more level to
+     * record its sub-fields
+     */
+    if (value instanceof Map<?, ?>)
+    {
+      for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
+      {
+        String[] attNames = new String[attName.length + 1];
+        System.arraycopy(attName, 0, attNames, 0, attName.length);
+        attNames[attName.length] = entry.getKey().toString();
+        addAttribute(featureType, description, entry.getValue(), attNames);
+      }
+      return;
+    }
+
+    String valueAsString = value.toString();
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts == null)
     {
-      atts = new TreeMap<String, AttributeData>(
-              String.CASE_INSENSITIVE_ORDER);
+      atts = new TreeMap<>(comparator);
       attributes.put(featureType, atts);
     }
     AttributeData attData = atts.get(attName);
@@ -181,7 +239,7 @@ public class FeatureAttributes
       attData = new AttributeData();
       atts.put(attName, attData);
     }
-    attData.addInstance(description, value);
+    attData.addInstance(description, valueAsString);
   }
 
   /**
@@ -192,10 +250,10 @@ public class FeatureAttributes
    * @param attName
    * @return
    */
-  public String getDescription(String featureType, String attName)
+  public String getDescription(String featureType, String... attName)
   {
     String desc = null;
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts != null)
     {
       AttributeData attData = atts.get(attName);
@@ -217,9 +275,9 @@ public class FeatureAttributes
    * @param attName
    * @return
    */
-  public float[] getMinMax(String featureType, String attName)
+  public float[] getMinMax(String featureType, String... attName)
   {
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts != null)
     {
       AttributeData attData = atts.get(attName);
@@ -238,19 +296,18 @@ public class FeatureAttributes
    * @param attName
    * @param description
    */
-  public void addDescription(String featureType, String attName,
-          String description)
+  public void addDescription(String featureType, String description,
+          String... attName)
   {
     if (featureType == null || attName == null)
     {
       return;
     }
   
-    Map<String, AttributeData> atts = attributes.get(featureType);
+    Map<String[], AttributeData> atts = attributes.get(featureType);
     if (atts == null)
     {
-      atts = new TreeMap<String, AttributeData>(
-              String.CASE_INSENSITIVE_ORDER);
+      atts = new TreeMap<>(comparator);
       attributes.put(featureType, atts);
     }
     AttributeData attData = atts.get(attName);