JAL-2069 update spike branch with latest
[jalview.git] / src / jalview / datamodel / features / FeatureAttributes.java
1 package jalview.datamodel.features;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.TreeMap;
9
10 /**
11  * A singleton class to hold the set of attributes known for each feature type
12  */
13 public class FeatureAttributes
14 {
15   private static FeatureAttributes instance = new FeatureAttributes();
16
17   private Map<String, Map<String, AttributeData>> attributes;
18
19   private class AttributeData
20   {
21     /*
22      * description(s) for this attribute, if known
23      * (different feature source might have differing descriptions)
24      */
25     List<String> description;
26
27     /*
28      * minimum value (of any numeric values recorded)
29      */
30     float min = 0f;
31
32     /*
33      * maximum value (of any numeric values recorded)
34      */
35     float max = 0f;
36
37     /*
38      * flag is set true if any numeric value is detected for this attribute
39      */
40     boolean hasValue = false;
41
42     /**
43      * Note one instance of this attribute, recording unique, non-null names,
44      * and the min/max of any numerical values
45      * 
46      * @param desc
47      * @param value
48      */
49     void addInstance(String desc, String value)
50     {
51       addDescription(desc);
52
53       if (value != null)
54       {
55         try
56         {
57           float f = Float.valueOf(value);
58           min = Float.min(min, f);
59           max = Float.max(max, f);
60           hasValue = true;
61         } catch (NumberFormatException e)
62         {
63           // ok, wasn't a number, ignore for min-max purposes
64         }
65       }
66     }
67
68     /**
69      * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
70      * @return
71      */
72     public String getDescription()
73     {
74       if (description != null && description.size() == 1)
75       {
76         return description.get(0);
77       }
78       return null;
79     }
80
81     /**
82      * Adds the given description to the list of known descriptions (without
83      * duplication)
84      * 
85      * @param desc
86      */
87     public void addDescription(String desc)
88     {
89       if (desc != null)
90       {
91         if (description == null)
92         {
93           description = new ArrayList<>();
94         }
95         if (!description.contains(desc))
96         {
97           description.add(desc);
98         }
99       }
100     }
101   }
102
103   /**
104    * Answers the singleton instance of this class
105    * 
106    * @return
107    */
108   public static FeatureAttributes getInstance()
109   {
110     return instance;
111   }
112
113   private FeatureAttributes()
114   {
115     attributes = new HashMap<>();
116   }
117
118   /**
119    * Answers the attributes known for the given feature type, in alphabetical
120    * order (not case sensitive), or an empty set if no attributes are known
121    * 
122    * @param featureType
123    * @return
124    */
125   public List<String> getAttributes(String featureType)
126   {
127     if (!attributes.containsKey(featureType))
128     {
129       return Collections.<String> emptyList();
130     }
131
132     return new ArrayList<>(attributes.get(featureType).keySet());
133   }
134
135   /**
136    * Answers true if at least one attribute is known for the given feature type,
137    * else false
138    * 
139    * @param featureType
140    * @return
141    */
142   public boolean hasAttributes(String featureType)
143   {
144     if (attributes.containsKey(featureType))
145     {
146       if (!attributes.get(featureType).isEmpty())
147       {
148         return true;
149       }
150     }
151     return false;
152   }
153
154   /**
155    * Records the given attribute name and description for the given feature
156    * type, and updates the min-max for any numeric value
157    * 
158    * @param featureType
159    * @param attName
160    * @param description
161    * @param value
162    */
163   public void addAttribute(String featureType, String attName,
164           String description, String value)
165   {
166     if (featureType == null || attName == null)
167     {
168       return;
169     }
170
171     Map<String, AttributeData> atts = attributes.get(featureType);
172     if (atts == null)
173     {
174       atts = new TreeMap<String, AttributeData>(
175               String.CASE_INSENSITIVE_ORDER);
176       attributes.put(featureType, atts);
177     }
178     AttributeData attData = atts.get(attName);
179     if (attData == null)
180     {
181       attData = new AttributeData();
182       atts.put(attName, attData);
183     }
184     attData.addInstance(description, value);
185   }
186
187   /**
188    * Answers the description of the given attribute for the given feature type,
189    * if known and unique, else null
190    * 
191    * @param featureType
192    * @param attName
193    * @return
194    */
195   public String getDescription(String featureType, String attName)
196   {
197     String desc = null;
198     Map<String, AttributeData> atts = attributes.get(featureType);
199     if (atts != null)
200     {
201       AttributeData attData = atts.get(attName);
202       if (attData != null)
203       {
204         desc = attData.getDescription();
205       }
206     }
207     return desc;
208   }
209
210   /**
211    * Answers the [min, max] value range of the given attribute for the given
212    * feature type, if known, else null. Attributes which only have text values
213    * would normally return null, however text values which happen to be numeric
214    * could result in a 'min-max' range.
215    * 
216    * @param featureType
217    * @param attName
218    * @return
219    */
220   public float[] getMinMax(String featureType, String attName)
221   {
222     Map<String, AttributeData> atts = attributes.get(featureType);
223     if (atts != null)
224     {
225       AttributeData attData = atts.get(attName);
226       if (attData != null && attData.hasValue)
227       {
228         return new float[] { attData.min, attData.max };
229       }
230     }
231     return null;
232   }
233
234   /**
235    * Records the given attribute description for the given feature type
236    * 
237    * @param featureType
238    * @param attName
239    * @param description
240    */
241   public void addDescription(String featureType, String attName,
242           String description)
243   {
244     if (featureType == null || attName == null)
245     {
246       return;
247     }
248   
249     Map<String, AttributeData> atts = attributes.get(featureType);
250     if (atts == null)
251     {
252       atts = new TreeMap<String, AttributeData>(
253               String.CASE_INSENSITIVE_ORDER);
254       attributes.put(featureType, atts);
255     }
256     AttributeData attData = atts.get(attName);
257     if (attData == null)
258     {
259       attData = new AttributeData();
260       atts.put(attName, attData);
261     }
262     attData.addDescription(description);
263   }
264 }