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;
33 * A singleton class to hold the set of attributes known for each feature type
35 public class FeatureAttributes
39 Character, Number, Mixed
42 private static FeatureAttributes instance = new FeatureAttributes();
45 * map, by feature type, of a map, by attribute name, of
46 * attribute description and min-max range (if known)
48 private Map<String, Map<String[], AttributeData>> attributes;
51 * a case-insensitive comparator so that attributes are ordered e.g.
57 private Comparator<String[]> comparator = new Comparator<String[]>()
60 public int compare(String[] o1, String[] o2)
63 while (i < o1.length || i < o2.length)
67 return o1.length <= i ? 0 : 1;
73 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
80 return 0; // same length and all matched
84 private class AttributeData
87 * description(s) for this attribute, if known
88 * (different feature source might have differing descriptions)
90 List<String> description;
93 * minimum value (of any numeric values recorded)
98 * maximum value (of any numeric values recorded)
103 * flag is set true if any numeric value is detected for this attribute
105 boolean hasValue = false;
110 * Note one instance of this attribute, recording unique, non-null
111 * descriptions, and the min/max of any numerical values
116 void addInstance(String desc, String value)
118 addDescription(desc);
122 value = value.trim();
125 * Parse numeric value unless we have previously
126 * seen text data for this attribute type
128 if (type == null || type == Datatype.Number)
132 float f = Float.valueOf(value);
133 min = hasValue ? Math.min(min, f) : f;
134 max = hasValue ? Math.max(max, f) : f;
136 type = (type == null || type == Datatype.Number)
139 } catch (NumberFormatException e)
142 * non-numeric data: treat attribute as Character (or Mixed)
144 type = (type == null || type == Datatype.Character)
156 * Answers the description of the attribute, if recorded and unique, or null
157 * if either no, or more than description is recorded
161 public String getDescription()
163 if (description != null && description.size() == 1)
165 return description.get(0);
170 public Datatype getType()
176 * Adds the given description to the list of known descriptions (without
181 public void addDescription(String desc)
185 if (description == null)
187 description = new ArrayList<>();
189 if (!description.contains(desc))
191 description.add(desc);
198 * Answers the singleton instance of this class
202 public static FeatureAttributes getInstance()
207 private FeatureAttributes()
209 attributes = new HashMap<>();
213 * Answers the attribute names known for the given feature type, in
214 * alphabetical order (not case sensitive), or an empty set if no attributes
215 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
216 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
221 public List<String[]> getAttributes(String featureType)
223 if (!attributes.containsKey(featureType))
225 return Collections.<String[]> emptyList();
228 return new ArrayList<>(attributes.get(featureType).keySet());
232 * Answers true if at least one attribute is known for the given feature type,
238 public boolean hasAttributes(String featureType)
240 if (attributes.containsKey(featureType))
242 if (!attributes.get(featureType).isEmpty())
251 * Records the given attribute name and description for the given feature
252 * type, and updates the min-max for any numeric value
259 public void addAttribute(String featureType, String description,
260 Object value, String... attName)
262 if (featureType == null || attName == null)
268 * if attribute value is a map, drill down one more level to
269 * record its sub-fields
271 if (value instanceof Map<?, ?>)
273 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
275 String[] attNames = new String[attName.length + 1];
276 System.arraycopy(attName, 0, attNames, 0, attName.length);
277 attNames[attName.length] = entry.getKey().toString();
278 addAttribute(featureType, description, entry.getValue(), attNames);
283 String valueAsString = value.toString();
284 Map<String[], AttributeData> atts = attributes.get(featureType);
287 atts = new TreeMap<>(comparator);
288 attributes.put(featureType, atts);
290 AttributeData attData = atts.get(attName);
293 attData = new AttributeData();
294 atts.put(attName, attData);
296 attData.addInstance(description, valueAsString);
300 * Answers the description of the given attribute for the given feature type,
301 * if known and unique, else null
307 public String getDescription(String featureType, String... attName)
310 Map<String[], AttributeData> atts = attributes.get(featureType);
313 AttributeData attData = atts.get(attName);
316 desc = attData.getDescription();
323 * Answers the [min, max] value range of the given attribute for the given
324 * feature type, if known, else null. Attributes with a mixture of text and
325 * numeric values are considered text (do not return a min-max range).
331 public float[] getMinMax(String featureType, String... attName)
333 Map<String[], AttributeData> atts = attributes.get(featureType);
336 AttributeData attData = atts.get(attName);
337 if (attData != null && attData.hasValue)
339 return new float[] { attData.min, attData.max };
346 * Records the given attribute description for the given feature type
352 public void addDescription(String featureType, String description,
355 if (featureType == null || attName == null)
360 Map<String[], AttributeData> atts = attributes.get(featureType);
363 atts = new TreeMap<>(comparator);
364 attributes.put(featureType, atts);
366 AttributeData attData = atts.get(attName);
369 attData = new AttributeData();
370 atts.put(attName, attData);
372 attData.addDescription(description);
376 * Answers the datatype of the feature, which is one of Character, Number or
377 * Mixed (or null if not known), as discovered from values recorded.
383 public Datatype getDatatype(String featureType, String... attName)
385 Map<String[], AttributeData> atts = attributes.get(featureType);
388 AttributeData attData = atts.get(attName);
391 return attData.getType();
398 * Resets all attribute metadata
406 * Resets attribute metadata for one feature type
410 public void clear(String featureType)
412 Map<String[], AttributeData> map = attributes.get(featureType);