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 ApplicationSingletonProvider.getInstance(FeatureAttributes.class);
50 private FeatureAttributes()
52 attributes = new HashMap<>();
56 * map, by feature type, of a map, by attribute name, of
57 * attribute description and min-max range (if known)
59 private Map<String, Map<String[], AttributeData>> attributes;
62 * a case-insensitive comparator so that attributes are ordered e.g.
68 private Comparator<String[]> comparator = new Comparator<String[]>()
71 public int compare(String[] o1, String[] o2)
74 while (i < o1.length || i < o2.length)
78 return o1.length <= i ? 0 : 1;
84 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
91 return 0; // same length and all matched
95 private class AttributeData
98 * description(s) for this attribute, if known
99 * (different feature source might have differing descriptions)
101 List<String> description;
104 * minimum value (of any numeric values recorded)
109 * maximum value (of any numeric values recorded)
114 * flag is set true if any numeric value is detected for this attribute
116 boolean hasValue = false;
121 * Note one instance of this attribute, recording unique, non-null
122 * descriptions, and the min/max of any numerical values
127 void addInstance(String desc, String value)
129 addDescription(desc);
133 value = value.trim();
140 * Parse numeric value unless we have previously
141 * seen text data for this attribute type
143 if ((type == null && couldBeNumber(value))
144 || type == Datatype.Number)
148 float f = Float.valueOf(value);
149 min = hasValue ? Math.min(min, f) : f;
150 max = hasValue ? Math.max(max, f) : f;
152 type = (type == null || type == Datatype.Number)
155 } catch (NumberFormatException e)
158 * non-numeric data: treat attribute as Character (or Mixed)
160 type = (type == null || type == Datatype.Character)
171 * if not a number, and not seen before...
173 type = Datatype.Character;
182 * Answers the description of the attribute, if recorded and unique, or null
183 * if either no, or more than description is recorded
187 public String getDescription()
189 if (description != null && description.size() == 1)
191 return description.get(0);
196 public Datatype getType()
202 * Adds the given description to the list of known descriptions (without
207 public void addDescription(String desc)
211 if (description == null)
213 description = new ArrayList<>();
215 if (!description.contains(desc))
217 description.add(desc);
224 * Answers the attribute names known for the given feature type, in
225 * alphabetical order (not case sensitive), or an empty set if no attributes
226 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
227 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
232 public List<String[]> getAttributes(String featureType)
234 if (!attributes.containsKey(featureType))
236 return Collections.<String[]> emptyList();
239 return new ArrayList<>(attributes.get(featureType).keySet());
243 * A partial check that the string is numeric - only checks the first
244 * character. Returns true if the first character is a digit, or if it is '.',
245 * '+' or '-' and not the only character. Otherwise returns false (including
246 * for an empty string). Note this is not a complete check as it returns true
252 public static boolean couldBeNumber(String f)
254 int len = f.length();
259 char ch = f.charAt(0);
267 return (ch <= '9' && ch >= '0');
271 * Answers true if at least one attribute is known for the given feature type,
277 public boolean hasAttributes(String featureType)
279 if (attributes.containsKey(featureType))
281 if (!attributes.get(featureType).isEmpty())
290 * Records the given attribute name and description for the given feature
291 * type, and updates the min-max for any numeric value
298 public void addAttribute(String featureType, String description,
299 Object value, String... attName)
301 if (featureType == null || attName == null)
307 * if attribute value is a map, drill down one more level to
308 * record its sub-fields
310 if (value instanceof Map<?, ?>)
312 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
314 String[] attNames = new String[attName.length + 1];
315 System.arraycopy(attName, 0, attNames, 0, attName.length);
316 attNames[attName.length] = entry.getKey().toString();
317 addAttribute(featureType, description, entry.getValue(), attNames);
322 String valueAsString = value.toString();
323 Map<String[], AttributeData> atts = attributes.get(featureType);
326 atts = new TreeMap<>(comparator);
327 attributes.put(featureType, atts);
329 AttributeData attData = atts.get(attName);
332 attData = new AttributeData();
333 atts.put(attName, attData);
335 attData.addInstance(description, valueAsString);
339 * Answers the description of the given attribute for the given feature type,
340 * if known and unique, else null
346 public String getDescription(String featureType, String... attName)
349 Map<String[], AttributeData> atts = attributes.get(featureType);
352 AttributeData attData = atts.get(attName);
355 desc = attData.getDescription();
362 * Answers the [min, max] value range of the given attribute for the given
363 * feature type, if known, else null. Attributes with a mixture of text and
364 * numeric values are considered text (do not return a min-max range).
370 public float[] getMinMax(String featureType, String... attName)
372 Map<String[], AttributeData> atts = attributes.get(featureType);
375 AttributeData attData = atts.get(attName);
376 if (attData != null && attData.hasValue)
378 return new float[] { attData.min, attData.max };
385 * Records the given attribute description for the given feature type
391 public void addDescription(String featureType, String description,
394 if (featureType == null || attName == null)
399 Map<String[], AttributeData> atts = attributes.get(featureType);
402 atts = new TreeMap<>(comparator);
403 attributes.put(featureType, atts);
405 AttributeData attData = atts.get(attName);
408 attData = new AttributeData();
409 atts.put(attName, attData);
411 attData.addDescription(description);
415 * Answers the datatype of the feature, which is one of Character, Number or
416 * Mixed (or null if not known), as discovered from values recorded.
422 public Datatype getDatatype(String featureType, String... attName)
424 Map<String[], AttributeData> atts = attributes.get(featureType);
427 AttributeData attData = atts.get(attName);
430 return attData.getType();
437 * Resets all attribute metadata
445 * Resets attribute metadata for one feature type
449 public void clear(String featureType)
451 Map<String[], AttributeData> map = attributes.get(featureType);