+ return out.toString();
+ }
+
+ /**
+ * Formats one feature as GFF and appends to the string buffer
+ */
+ private void formatGffFeature(StringBuilder out, SequenceI seq,
+ SequenceFeature sf)
+ {
+ String source = sf.featureGroup;
+ if (source == null)
+ {
+ source = sf.getDescription();
+ }
+
+ out.append(seq.getName());
+ out.append(TAB);
+ out.append(source);
+ out.append(TAB);
+ out.append(sf.type);
+ out.append(TAB);
+ out.append(sf.begin);
+ out.append(TAB);
+ out.append(sf.end);
+ out.append(TAB);
+ out.append(sf.score);
+ out.append(TAB);
+
+ int strand = sf.getStrand();
+ out.append(strand == 1 ? "+" : (strand == -1 ? "-" : "."));
+ out.append(TAB);
+
+ String phase = sf.getPhase();
+ out.append(phase == null ? "." : phase);
+
+ if (sf.otherDetails != null && !sf.otherDetails.isEmpty())
+ {
+ Map<String, Object> map = sf.otherDetails;
+ formatAttributes(out, map);
+ }
+ }
+
+ /**
+ * A helper method that outputs attributes stored in the map as
+ * semicolon-delimited values e.g.
+ *
+ * <pre>
+ * AC_Male=0;AF_NFE=0.00000e 00;Hom_FIN=0;GQ_MEDIAN=9
+ * </pre>
+ *
+ * A map-valued attribute is formatted as a comma-delimited list within braces,
+ * for example
+ *
+ * <pre>
+ * jvmap_CSQ={ALLELE_NUM=1,UNIPARC=UPI0002841053,Feature=ENST00000585561}
+ * </pre>
+ *
+ * The {@code jvmap_} prefix designates a values map and is removed if the value
+ * is parsed when read in. (The GFF3 specification allows 'semi-structured data'
+ * to be represented provided the attribute name begins with a lower case
+ * letter.)
+ *
+ * @param sb
+ * @param map
+ * @see http://gmod.org/wiki/GFF3#GFF3_Format
+ */
+ void formatAttributes(StringBuilder sb, Map<String, Object> map)
+ {
+ sb.append(TAB);
+ boolean first = true;
+ for (String key : map.keySet())
+ {
+ if (SequenceFeature.STRAND.equals(key)
+ || SequenceFeature.PHASE.equals(key))
+ {
+ /*
+ * values stashed in map but output to their own columns
+ */
+ continue;
+ }
+ {
+ if (!first)
+ {
+ sb.append(";");