2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.datamodel.features;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Map.Entry;
30 import java.util.TreeMap;
32 import jalview.bin.ApplicationSingletonProvider;
33 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
36 * A singleton class to hold the set of attributes known for each feature type
38 public class FeatureAttributes implements ApplicationSingletonI
42 Character, Number, Mixed
45 public static FeatureAttributes getInstance()
47 return (FeatureAttributes) ApplicationSingletonProvider
48 .getInstance(FeatureAttributes.class);
52 * map, by feature type, of a map, by attribute name, of
53 * attribute description and min-max range (if known)
55 private Map<String, Map<String[], AttributeData>> attributes;
58 * a case-insensitive comparator so that attributes are ordered e.g.
64 private Comparator<String[]> comparator = new Comparator<String[]>()
67 public int compare(String[] o1, String[] o2)
70 while (i < o1.length || i < o2.length)
74 return o1.length <= i ? 0 : 1;
80 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
87 return 0; // same length and all matched
91 private class AttributeData
94 * description(s) for this attribute, if known
95 * (different feature source might have differing descriptions)
97 List<String> description;
100 * minimum value (of any numeric values recorded)
105 * maximum value (of any numeric values recorded)
110 * flag is set true if any numeric value is detected for this attribute
112 boolean hasValue = false;
117 * Note one instance of this attribute, recording unique, non-null
118 * descriptions, and the min/max of any numerical values
123 void addInstance(String desc, String value)
125 addDescription(desc);
129 value = value.trim();
132 * Parse numeric value unless we have previously
133 * seen text data for this attribute type
135 if (type == null || type == Datatype.Number)
139 float f = Float.valueOf(value);
140 min = hasValue ? Math.min(min, f) : f;
141 max = hasValue ? Math.max(max, f) : f;
143 type = (type == null || type == Datatype.Number)
146 } catch (NumberFormatException e)
149 * non-numeric data: treat attribute as Character (or Mixed)
151 type = (type == null || type == Datatype.Character)
163 * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
166 public String getDescription()
168 if (description != null && description.size() == 1)
170 return description.get(0);
175 public Datatype getType()
181 * Adds the given description to the list of known descriptions (without
186 public void addDescription(String desc)
190 if (description == null)
192 description = new ArrayList<>();
194 if (!description.contains(desc))
196 description.add(desc);
202 private FeatureAttributes()
204 attributes = new HashMap<>();
208 * Answers the attribute names known for the given feature type, in
209 * alphabetical order (not case sensitive), or an empty set if no attributes
210 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
211 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
216 public List<String[]> getAttributes(String featureType)
218 if (!attributes.containsKey(featureType))
220 return Collections.<String[]> emptyList();
223 return new ArrayList<>(attributes.get(featureType).keySet());
227 * Answers true if at least one attribute is known for the given feature type,
233 public boolean hasAttributes(String featureType)
235 if (attributes.containsKey(featureType))
237 if (!attributes.get(featureType).isEmpty())
246 * Records the given attribute name and description for the given feature
247 * type, and updates the min-max for any numeric value
254 public void addAttribute(String featureType, String description,
255 Object value, String... attName)
257 if (featureType == null || attName == null)
263 * if attribute value is a map, drill down one more level to
264 * record its sub-fields
266 if (value instanceof Map<?, ?>)
268 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
270 String[] attNames = new String[attName.length + 1];
271 System.arraycopy(attName, 0, attNames, 0, attName.length);
272 attNames[attName.length] = entry.getKey().toString();
273 addAttribute(featureType, description, entry.getValue(), attNames);
278 String valueAsString = value.toString();
279 Map<String[], AttributeData> atts = attributes.get(featureType);
282 atts = new TreeMap<>(comparator);
283 attributes.put(featureType, atts);
285 AttributeData attData = atts.get(attName);
288 attData = new AttributeData();
289 atts.put(attName, attData);
291 attData.addInstance(description, valueAsString);
295 * Answers the description of the given attribute for the given feature type,
296 * if known and unique, else null
302 public String getDescription(String featureType, String... attName)
305 Map<String[], AttributeData> atts = attributes.get(featureType);
308 AttributeData attData = atts.get(attName);
311 desc = attData.getDescription();
318 * Answers the [min, max] value range of the given attribute for the given
319 * feature type, if known, else null. Attributes with a mixture of text and
320 * numeric values are considered text (do not return a min-max range).
326 public float[] getMinMax(String featureType, String... attName)
328 Map<String[], AttributeData> atts = attributes.get(featureType);
331 AttributeData attData = atts.get(attName);
332 if (attData != null && attData.hasValue)
334 return new float[] { attData.min, attData.max };
341 * Records the given attribute description for the given feature type
347 public void addDescription(String featureType, String description,
350 if (featureType == null || attName == null)
355 Map<String[], AttributeData> atts = attributes.get(featureType);
358 atts = new TreeMap<>(comparator);
359 attributes.put(featureType, atts);
361 AttributeData attData = atts.get(attName);
364 attData = new AttributeData();
365 atts.put(attName, attData);
367 attData.addDescription(description);
371 * Answers the datatype of the feature, which is one of Character, Number or
372 * Mixed (or null if not known), as discovered from values recorded.
378 public Datatype getDatatype(String featureType, String... attName)
380 Map<String[], AttributeData> atts = attributes.get(featureType);
383 AttributeData attData = atts.get(attName);
386 return attData.getType();
393 * Resets all attribute metadata
401 * Resets attribute metadata for one feature type
405 public void clear(String featureType)
407 Map<String[], AttributeData> map = attributes.get(featureType);