X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2Ffeatures%2FFeatureAttributes.java;h=c15650dcbd58cdc2d6ea47e524d78b5081799e1e;hb=30119dce1634085c41372d55b528a7a878b03b23;hp=ed6575014c6d57bb7df43dffa7b4f6ab05f89a51;hpb=ee8d9bad4fcb0109fd38dc362b77076d3046fec4;p=jalview.git diff --git a/src/jalview/datamodel/features/FeatureAttributes.java b/src/jalview/datamodel/features/FeatureAttributes.java index ed65750..c15650d 100644 --- a/src/jalview/datamodel/features/FeatureAttributes.java +++ b/src/jalview/datamodel/features/FeatureAttributes.java @@ -1,10 +1,32 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ 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; /** @@ -12,9 +34,52 @@ import java.util.TreeMap; */ public class FeatureAttributes { + public enum Datatype + { + Character, Number, Mixed + } + private static FeatureAttributes instance = new FeatureAttributes(); - private Map> attributes; + /* + * map, by feature type, of a map, by attribute name, of + * attribute description and min-max range (if known) + */ + private Map> attributes; + + /* + * a case-insensitive comparator so that attributes are ordered e.g. + * AC + * af + * CSQ:AFR_MAF + * CSQ:Allele + */ + private Comparator comparator = new Comparator() + { + @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 { @@ -34,35 +99,54 @@ public class FeatureAttributes */ float max = 0f; + /* + * flag is set true if any numeric value is detected for this attribute + */ + boolean hasValue = false; + + Datatype type; + /** - * Note one instance of this attribute, recording unique, non-null names, - * and the min/max of any numerical values + * Note one instance of this attribute, recording unique, non-null + * descriptions, and the min/max of any numerical values * * @param desc * @param value */ void addInstance(String desc, String value) { - if (desc != null) + addDescription(desc); + + if (value != null) { - if (description == null) - { - description = new ArrayList<>(); - } - if (!description.contains(desc)) - { - description.add(desc); - } - if (value != null) + value = value.trim(); + + /* + * Parse numeric value unless we have previously + * seen text data for this attribute type + */ + if (type == null || type == Datatype.Number) { try { float f = Float.valueOf(value); - min = Float.min(min, f); - max = Float.max(max, f); + min = hasValue ? Float.min(min, f) : f; + max = hasValue ? Float.max(max, f) : f; + hasValue = true; + type = (type == null || type == Datatype.Number) + ? Datatype.Number + : Datatype.Mixed; } catch (NumberFormatException e) { - // ok, wasn't a number + /* + * non-numeric data: treat attribute as Character (or Mixed) + */ + type = (type == null || type == Datatype.Character) + ? Datatype.Character + : Datatype.Mixed; + min = 0f; + max = 0f; + hasValue = false; } } } @@ -80,6 +164,32 @@ public class FeatureAttributes } return null; } + + public Datatype getType() + { + return type; + } + + /** + * Adds the given description to the list of known descriptions (without + * duplication) + * + * @param desc + */ + public void addDescription(String desc) + { + if (desc != null) + { + if (description == null) + { + description = new ArrayList<>(); + } + if (!description.contains(desc)) + { + description.add(desc); + } + } + } } /** @@ -98,17 +208,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 getAttributes(String featureType) + public List getAttributes(String featureType) { if (!attributes.containsKey(featureType)) { - return Collections. emptyList(); + return Collections. emptyList(); } return new ArrayList<>(attributes.get(featureType).keySet()); @@ -138,23 +250,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 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 atts = attributes.get(featureType); if (atts == null) { - atts = new TreeMap( - String.CASE_INSENSITIVE_ORDER); + atts = new TreeMap<>(comparator); attributes.put(featureType, atts); } AttributeData attData = atts.get(attName); @@ -163,7 +291,7 @@ public class FeatureAttributes attData = new AttributeData(); atts.put(attName, attData); } - attData.addInstance(description, value); + attData.addInstance(description, valueAsString); } /** @@ -174,10 +302,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 atts = attributes.get(featureType); + Map atts = attributes.get(featureType); if (atts != null) { AttributeData attData = atts.get(attName); @@ -188,4 +316,102 @@ public class FeatureAttributes } return desc; } + + /** + * Answers the [min, max] value range of the given attribute for the given + * feature type, if known, else null. Attributes with a mixture of text and + * numeric values are considered text (do not return a min-max range). + * + * @param featureType + * @param attName + * @return + */ + public float[] getMinMax(String featureType, String... attName) + { + Map atts = attributes.get(featureType); + if (atts != null) + { + AttributeData attData = atts.get(attName); + if (attData != null && attData.hasValue) + { + return new float[] { attData.min, attData.max }; + } + } + return null; + } + + /** + * Records the given attribute description for the given feature type + * + * @param featureType + * @param attName + * @param description + */ + public void addDescription(String featureType, String description, + String... attName) + { + if (featureType == null || attName == null) + { + return; + } + + Map atts = attributes.get(featureType); + if (atts == null) + { + atts = new TreeMap<>(comparator); + attributes.put(featureType, atts); + } + AttributeData attData = atts.get(attName); + if (attData == null) + { + attData = new AttributeData(); + atts.put(attName, attData); + } + attData.addDescription(description); + } + + /** + * Answers the datatype of the feature, which is one of Character, Number or + * Mixed (or null if not known), as discovered from values recorded. + * + * @param featureType + * @param attName + * @return + */ + public Datatype getDatatype(String featureType, String... attName) + { + Map atts = attributes.get(featureType); + if (atts != null) + { + AttributeData attData = atts.get(attName); + if (attData != null) + { + return attData.getType(); + } + } + return null; + } + + /** + * Resets all attribute metadata + */ + public void clear() + { + attributes.clear(); + } + + /** + * Resets attribute metadata for one feature type + * + * @param featureType + */ + public void clear(String featureType) + { + Map map = attributes.get(featureType); + if (map != null) + { + map.clear(); + } + + } }