+ * Answers the VCF FILTER value for the variant - or an approximation to it.
+ * This field is either PASS, or a semi-colon separated list of filters not
+ * passed. htsjdk saves filters as a HashSet, so the order when reassembled into
+ * a list may be different.
+ *
+ * @param variant
+ * @return
+ */
+ String getFilter(VariantContext variant)
+ {
+ Set<String> filters = variant.getFilters();
+ if (filters.isEmpty())
+ {
+ return NO_VALUE;
+ }
+ Iterator<String> iterator = filters.iterator();
+ String first = iterator.next();
+ if (filters.size() == 1)
+ {
+ return first;
+ }
+
+ StringBuilder sb = new StringBuilder(first);
+ while (iterator.hasNext())
+ {
+ sb.append(";").append(iterator.next());
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Adds one feature attribute unless the value is null, empty or '.'
+ *
+ * @param sf
+ * @param key
+ * @param value
+ */
+ void addFeatureAttribute(SequenceFeature sf, String key, String value)
+ {
+ if (value != null && !value.isEmpty() && !NO_VALUE.equals(value))
+ {
+ sf.setValue(key, value);
+ }
+ }
+
+ /**
+ * Determines the Sequence Ontology term to use for the variant feature type in
+ * Jalview. The default is 'sequence_variant', but a more specific term is used
+ * if:
+ * <ul>
+ * <li>VEP (or SnpEff) Consequence annotation is included in the VCF</li>
+ * <li>sequence id can be matched to VEP Feature (or SnpEff Feature_ID)</li>
+ * </ul>
+ *
+ * @param consequence
+ * @return
+ * @see http://www.sequenceontology.org/browser/current_svn/term/SO:0001060
+ */
+ String getOntologyTerm(String consequence)
+ {
+ String type = SequenceOntologyI.SEQUENCE_VARIANT;
+
+ /*
+ * could we associate Consequence data with this allele and feature (transcript)?
+ * if so, prefer the consequence term from that data
+ */
+ if (csqAlleleFieldIndex == -1) // && snpEffAlleleFieldIndex == -1
+ {
+ /*
+ * no Consequence data so we can't refine the ontology term
+ */
+ return type;
+ }
+
+ if (consequence != null)
+ {
+ String[] csqFields = consequence.split(PIPE_REGEX);
+ if (csqFields.length > csqConsequenceFieldIndex)
+ {
+ type = csqFields[csqConsequenceFieldIndex];
+ }
+ }
+ else
+ {
+ // todo the same for SnpEff consequence data matching if wanted
+ }
+
+ /*
+ * if of the form (e.g.) missense_variant&splice_region_variant,
+ * just take the first ('most severe') consequence
+ */
+ if (type != null)
+ {
+ int pos = type.indexOf('&');
+ if (pos > 0)
+ {
+ type = type.substring(0, pos);
+ }
+ }
+ return type;
+ }
+
+ /**
+ * Returns matched consequence data if it can be found, else null.
+ * <ul>
+ * <li>inspects the VCF data for key 'vcfInfoId'</li>
+ * <li>splits this on comma (to distinct consequences)</li>
+ * <li>returns the first consequence (if any) where</li>
+ * <ul>
+ * <li>the allele matches the altAlleleIndex'th allele of variant</li>
+ * <li>the feature matches the sequence name (e.g. transcript id)</li>
+ * </ul>
+ * </ul>
+ * If matched, the consequence is returned (as pipe-delimited fields).
+ *
+ * @param variant
+ * @param vcfInfoId
+ * @param altAlleleIndex
+ * @param alleleFieldIndex
+ * @param alleleNumberFieldIndex
+ * @param seqName
+ * @param featureFieldIndex
+ * @return
+ */
+ private String getConsequenceForAlleleAndFeature(VariantContext variant,
+ String vcfInfoId, int altAlleleIndex, int alleleFieldIndex,
+ int alleleNumberFieldIndex,
+ String seqName, int featureFieldIndex)
+ {
+ if (alleleFieldIndex == -1 || featureFieldIndex == -1)
+ {
+ return null;
+ }
+ Object value = variant.getAttribute(vcfInfoId);
+
+ if (value == null || !(value instanceof List<?>))
+ {
+ return null;
+ }
+
+ /*
+ * inspect each consequence in turn (comma-separated blocks
+ * extracted by htsjdk)
+ */
+ List<String> consequences = (List<String>) value;
+
+ for (String consequence : consequences)
+ {
+ String[] csqFields = consequence.split(PIPE_REGEX);
+ if (csqFields.length > featureFieldIndex)
+ {
+ String featureIdentifier = csqFields[featureFieldIndex];
+ if (featureIdentifier.length() > 4
+ && seqName.indexOf(featureIdentifier.toLowerCase()) > -1)
+ {
+ /*
+ * feature (transcript) matched - now check for allele match
+ */
+ if (matchAllele(variant, altAlleleIndex, csqFields,
+ alleleFieldIndex, alleleNumberFieldIndex))
+ {
+ return consequence;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean matchAllele(VariantContext variant, int altAlleleIndex,
+ String[] csqFields, int alleleFieldIndex,
+ int alleleNumberFieldIndex)
+ {
+ /*
+ * if ALLELE_NUM is present, it must match altAlleleIndex
+ * NB first alternate allele is 1 for ALLELE_NUM, 0 for altAlleleIndex
+ */
+ if (alleleNumberFieldIndex > -1)
+ {
+ if (csqFields.length <= alleleNumberFieldIndex)
+ {
+ return false;
+ }
+ String alleleNum = csqFields[alleleNumberFieldIndex];
+ return String.valueOf(altAlleleIndex + 1).equals(alleleNum);
+ }
+
+ /*
+ * else consequence allele must match variant allele
+ */
+ if (alleleFieldIndex > -1 && csqFields.length > alleleFieldIndex)
+ {
+ String csqAllele = csqFields[alleleFieldIndex];
+ String vcfAllele = variant.getAlternateAllele(altAlleleIndex)
+ .getBaseString();
+ return csqAllele.equals(vcfAllele);
+ }
+ return false;
+ }
+
+ /**