+ /*
+ * map, by feature type, of a map, by attribute name, of
+ * attribute description and min-max range (if known)
+ */
+ private Map<String, Map<String[], AttributeData>> attributes;
+
+ /*
+ * a case-insensitive comparator so that attributes are ordered e.g.
+ * AC
+ * af
+ * CSQ:AFR_MAF
+ * CSQ:Allele
+ */
+ private Comparator<String[]> comparator = new Comparator<String[]>()
+ {
+ @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
+ {
+ /*
+ * description(s) for this attribute, if known
+ * (different feature source might have differing descriptions)
+ */
+ List<String> description;
+
+ /*
+ * minimum value (of any numeric values recorded)
+ */
+ float min = 0f;
+
+ /*
+ * maximum value (of any numeric values recorded)
+ */
+ 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
+ * descriptions, and the min/max of any numerical values
+ *
+ * @param desc
+ * @param value
+ */
+ void addInstance(String desc, String value)
+ {
+ addDescription(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 = 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)
+ {
+ /*
+ * 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;
+ }
+ }
+ }
+ }
+
+ /**
+ * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
+ * @return
+ */
+ public String getDescription()
+ {
+ if (description != null && description.size() == 1)
+ {
+ return description.get(0);
+ }
+ 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);
+ }
+ }
+ }
+ }