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.
23 import jalview.analysis.AlignmentUtils;
24 import jalview.analysis.SequenceIdMatcher;
25 import jalview.api.AlignViewportI;
26 import jalview.api.FeatureColourI;
27 import jalview.api.FeaturesSourceI;
28 import jalview.datamodel.AlignedCodonFrame;
29 import jalview.datamodel.Alignment;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.SequenceDummy;
32 import jalview.datamodel.SequenceFeature;
33 import jalview.datamodel.SequenceI;
34 import jalview.io.gff.GffHelperBase;
35 import jalview.io.gff.GffHelperFactory;
36 import jalview.io.gff.GffHelperI;
37 import jalview.schemes.FeatureColour;
38 import jalview.util.ColorUtils;
39 import jalview.util.MapList;
40 import jalview.util.ParseHtmlBodyAndLinks;
41 import jalview.util.StringUtils;
43 import java.awt.Color;
44 import java.io.IOException;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.List;
51 import java.util.Map.Entry;
54 * Parses and writes features files, which may be in Jalview, GFF2 or GFF3
55 * format. These are tab-delimited formats but with differences in the use of
58 * A Jalview feature file may define feature colours and then declare that the
59 * remainder of the file is in GFF format with the line 'GFF'.
61 * GFF3 files may include alignment mappings for features, which Jalview will
62 * attempt to model, and may include sequence data following a ##FASTA line.
69 public class FeaturesFile extends AlignFile implements FeaturesSourceI
71 private static final String ID_NOT_SPECIFIED = "ID_NOT_SPECIFIED";
73 private static final String NOTE = "Note";
75 protected static final String GFF_VERSION = "##gff-version";
77 private AlignmentI lastmatchedAl = null;
79 private SequenceIdMatcher matcher = null;
81 protected AlignmentI dataset;
83 protected int gffVersion;
86 * Creates a new FeaturesFile object.
93 * Constructor which does not parse the file immediately
99 public FeaturesFile(String file, DataSourceType paste)
102 super(false, file, paste);
107 * @throws IOException
109 public FeaturesFile(FileParse source) throws IOException
115 * Constructor that optionally parses the file immediately
117 * @param parseImmediately
120 * @throws IOException
122 public FeaturesFile(boolean parseImmediately, String file,
123 DataSourceType type) throws IOException
125 super(parseImmediately, file, type);
129 * Parse GFF or sequence features file using case-independent matching,
133 * - alignment/dataset containing sequences that are to be annotated
135 * - hashtable to store feature colour definitions
137 * - process html strings into plain text
138 * @return true if features were added
140 public boolean parse(AlignmentI align,
141 Map<String, FeatureColourI> colours, boolean removeHTML)
143 return parse(align, colours, removeHTML, false);
147 * Extends the default addProperties by also adding peptide-to-cDNA mappings
148 * (if any) derived while parsing a GFF file
151 public void addProperties(AlignmentI al)
153 super.addProperties(al);
154 if (dataset != null && dataset.getCodonFrames() != null)
156 AlignmentI ds = (al.getDataset() == null) ? al : al.getDataset();
157 for (AlignedCodonFrame codons : dataset.getCodonFrames())
159 ds.addCodonFrame(codons);
165 * Parse GFF or Jalview format sequence features file
168 * - alignment/dataset containing sequences that are to be annotated
170 * - hashtable to store feature colour definitions
172 * - process html strings into plain text
173 * @param relaxedIdmatching
174 * - when true, ID matches to compound sequence IDs are allowed
175 * @return true if features were added
177 public boolean parse(AlignmentI align,
178 Map<String, FeatureColourI> colours, boolean removeHTML,
179 boolean relaxedIdmatching)
181 Map<String, String> gffProps = new HashMap<>();
183 * keep track of any sequences we try to create from the data
185 List<SequenceI> newseqs = new ArrayList<>();
191 String featureGroup = null;
193 while ((line = nextLine()) != null)
195 // skip comments/process pragmas
196 if (line.length() == 0 || line.startsWith("#"))
198 if (line.toLowerCase().startsWith("##"))
200 processGffPragma(line, gffProps, align, newseqs);
205 gffColumns = line.split("\\t"); // tab as regex
206 if (gffColumns.length == 1)
208 if (line.trim().equalsIgnoreCase("GFF"))
211 * Jalview features file with appended GFF
212 * assume GFF2 (though it may declare ##gff-version 3)
219 if (gffColumns.length > 1 && gffColumns.length < 4)
222 * if 2 or 3 tokens, we anticipate either 'startgroup', 'endgroup' or
223 * a feature type colour specification
225 String ft = gffColumns[0];
226 if (ft.equalsIgnoreCase("startgroup"))
228 featureGroup = gffColumns[1];
230 else if (ft.equalsIgnoreCase("endgroup"))
232 // We should check whether this is the current group,
233 // but at present there's no way of showing more than 1 group
238 String colscheme = gffColumns[1];
239 FeatureColourI colour = FeatureColour
240 .parseJalviewFeatureColour(colscheme);
243 colours.put(ft, colour);
250 * if not a comment, GFF pragma, startgroup, endgroup or feature
251 * colour specification, that just leaves a feature details line
252 * in either Jalview or GFF format
256 parseJalviewFeature(line, gffColumns, align, colours, removeHTML,
257 relaxedIdmatching, featureGroup);
261 parseGff(gffColumns, align, relaxedIdmatching, newseqs);
265 } catch (Exception ex)
267 // should report somewhere useful for UI if necessary
268 warningMessage = ((warningMessage == null) ? "" : warningMessage)
269 + "Parsing error at\n" + line;
270 System.out.println("Error parsing feature file: " + ex + "\n" + line);
271 ex.printStackTrace(System.err);
277 * experimental - add any dummy sequences with features to the alignment
278 * - we need them for Ensembl feature extraction - though maybe not otherwise
280 for (SequenceI newseq : newseqs)
282 if (newseq.getFeatures().hasFeatures())
284 align.addSequence(newseq);
291 * Try to parse a Jalview format feature specification and add it as a
292 * sequence feature to any matching sequences in the alignment. Returns true
293 * if successful (a feature was added), or false if not.
298 * @param featureColours
300 * @param relaxedIdmatching
301 * @param featureGroup
303 protected boolean parseJalviewFeature(String line, String[] gffColumns,
304 AlignmentI alignment, Map<String, FeatureColourI> featureColours,
305 boolean removeHTML, boolean relaxedIdMatching,
309 * tokens: description seqid seqIndex start end type [score]
311 if (gffColumns.length < 6)
313 System.err.println("Ignoring feature line '" + line
314 + "' with too few columns (" + gffColumns.length + ")");
317 String desc = gffColumns[0];
318 String seqId = gffColumns[1];
319 SequenceI seq = findSequence(seqId, alignment, null, relaxedIdMatching);
321 if (!ID_NOT_SPECIFIED.equals(seqId))
323 seq = findSequence(seqId, alignment, null, relaxedIdMatching);
329 String seqIndex = gffColumns[2];
332 int idx = Integer.parseInt(seqIndex);
333 seq = alignment.getSequenceAt(idx);
334 } catch (NumberFormatException ex)
336 System.err.println("Invalid sequence index: " + seqIndex);
342 System.out.println("Sequence not found: " + line);
346 int startPos = Integer.parseInt(gffColumns[3]);
347 int endPos = Integer.parseInt(gffColumns[4]);
349 String ft = gffColumns[5];
351 if (!featureColours.containsKey(ft))
354 * Perhaps an old style groups file with no colours -
355 * synthesize a colour from the feature type
357 Color colour = ColorUtils.createColourFromName(ft);
358 featureColours.put(ft, new FeatureColour(colour));
360 SequenceFeature sf = null;
361 if (gffColumns.length > 6)
363 float score = Float.NaN;
366 score = new Float(gffColumns[6]).floatValue();
367 } catch (NumberFormatException ex)
369 sf = new SequenceFeature(ft, desc, startPos, endPos, featureGroup);
371 sf = new SequenceFeature(ft, desc, startPos, endPos, score,
376 sf = new SequenceFeature(ft, desc, startPos, endPos, featureGroup);
379 parseDescriptionHTML(sf, removeHTML);
381 seq.addSequenceFeature(sf);
384 && (seq = alignment.findName(seq, seqId, false)) != null)
386 seq.addSequenceFeature(new SequenceFeature(sf));
392 * clear any temporary handles used to speed up ID matching
394 protected void resetMatcher()
396 lastmatchedAl = null;
401 * Returns a sequence matching the given id, as follows
403 * <li>strict matching is on exact sequence name</li>
404 * <li>relaxed matching allows matching on a token within the sequence name,
406 * <li>first tries to find a match in the alignment sequences</li>
407 * <li>else tries to find a match in the new sequences already generated while
408 * parsing the features file</li>
409 * <li>else creates a new placeholder sequence, adds it to the new sequences
410 * list, and returns it</li>
416 * @param relaxedIdMatching
420 protected SequenceI findSequence(String seqId, AlignmentI align,
421 List<SequenceI> newseqs, boolean relaxedIdMatching)
423 // TODO encapsulate in SequenceIdMatcher, share the matcher
424 // with the GffHelper (removing code duplication)
425 SequenceI match = null;
426 if (relaxedIdMatching)
428 if (lastmatchedAl != align)
430 lastmatchedAl = align;
431 matcher = new SequenceIdMatcher(align.getSequencesArray());
434 matcher.addAll(newseqs);
437 match = matcher.findIdMatch(seqId);
441 match = align.findName(seqId, true);
442 if (match == null && newseqs != null)
444 for (SequenceI m : newseqs)
446 if (seqId.equals(m.getName()))
454 if (match == null && newseqs != null)
456 match = new SequenceDummy(seqId);
457 if (relaxedIdMatching)
459 matcher.addAll(Arrays.asList(new SequenceI[] { match }));
461 // add dummy sequence to the newseqs list
467 public void parseDescriptionHTML(SequenceFeature sf, boolean removeHTML)
469 if (sf.getDescription() == null)
473 ParseHtmlBodyAndLinks parsed = new ParseHtmlBodyAndLinks(
474 sf.getDescription(), removeHTML, newline);
478 sf.setDescription(parsed.getNonHtmlContent());
481 for (String link : parsed.getLinks())
488 * Returns contents of a Jalview format features file, for visible features,
489 * as filtered by type and group. Features with a null group are displayed if
490 * their feature type is visible. Non-positional features may optionally be
491 * included (with no check on type or group).
496 * map of colour for each visible feature type
497 * @param visibleFeatureGroups
498 * @param includeNonPositional
499 * if true, include non-positional features (regardless of group or
503 public String printJalviewFormat(SequenceI[] sequences,
504 Map<String, FeatureColourI> visible,
505 List<String> visibleFeatureGroups, boolean includeNonPositional)
507 if (!includeNonPositional && (visible == null || visible.isEmpty()))
509 // no point continuing.
510 return "No Features Visible";
514 * write out feature colours (if we know them)
516 // TODO: decide if feature links should also be written here ?
517 StringBuilder out = new StringBuilder(256);
520 for (Entry<String, FeatureColourI> featureColour : visible.entrySet())
522 FeatureColourI colour = featureColour.getValue();
523 out.append(colour.toJalviewFormat(featureColour.getKey())).append(
528 String[] types = visible == null ? new String[0] : visible.keySet()
529 .toArray(new String[visible.keySet().size()]);
532 * sort groups alphabetically, and ensure that features with a
533 * null or empty group are output after those in named groups
535 List<String> sortedGroups = new ArrayList<>(visibleFeatureGroups);
536 sortedGroups.remove(null);
537 sortedGroups.remove("");
538 Collections.sort(sortedGroups);
539 sortedGroups.add(null);
540 sortedGroups.add("");
542 boolean foundSome = false;
545 * first output any non-positional features
547 if (includeNonPositional)
549 for (int i = 0; i < sequences.length; i++)
551 String sequenceName = sequences[i].getName();
552 for (SequenceFeature feature : sequences[i].getFeatures()
553 .getNonPositionalFeatures())
556 out.append(formatJalviewFeature(sequenceName, feature));
561 for (String group : sortedGroups)
563 boolean isNamedGroup = (group != null && !"".equals(group));
567 out.append("STARTGROUP").append(TAB);
573 * output positional features within groups
575 for (int i = 0; i < sequences.length; i++)
577 String sequenceName = sequences[i].getName();
578 List<SequenceFeature> features = new ArrayList<>();
579 if (types.length > 0)
581 features.addAll(sequences[i].getFeatures().getFeaturesForGroup(
582 true, group, types));
585 for (SequenceFeature sequenceFeature : features)
588 out.append(formatJalviewFeature(sequenceName, sequenceFeature));
594 out.append("ENDGROUP").append(TAB);
600 return foundSome ? out.toString() : "No Features Visible";
605 * @param sequenceName
606 * @param sequenceFeature
608 protected String formatJalviewFeature(
609 String sequenceName, SequenceFeature sequenceFeature)
611 StringBuilder out = new StringBuilder(64);
612 if (sequenceFeature.description == null
613 || sequenceFeature.description.equals(""))
615 out.append(sequenceFeature.type).append(TAB);
619 if (sequenceFeature.links != null
620 && sequenceFeature.getDescription().indexOf("<html>") == -1)
622 out.append("<html>");
625 out.append(sequenceFeature.description);
626 if (sequenceFeature.links != null)
628 for (int l = 0; l < sequenceFeature.links.size(); l++)
630 String label = sequenceFeature.links.elementAt(l);
631 String href = label.substring(label.indexOf("|") + 1);
632 label = label.substring(0, label.indexOf("|"));
634 if (sequenceFeature.description.indexOf(href) == -1)
636 out.append(" <a href=\"" + href + "\">" + label + "</a>");
640 if (sequenceFeature.getDescription().indexOf("</html>") == -1)
642 out.append("</html>");
648 out.append(sequenceName);
649 out.append("\t-1\t");
650 out.append(sequenceFeature.begin);
652 out.append(sequenceFeature.end);
654 out.append(sequenceFeature.type);
655 if (!Float.isNaN(sequenceFeature.score))
658 out.append(sequenceFeature.score);
662 return out.toString();
666 * Parse method that is called when a GFF file is dragged to the desktop
671 AlignViewportI av = getViewport();
674 if (av.getAlignment() != null)
676 dataset = av.getAlignment().getDataset();
680 // working in the applet context ?
681 dataset = av.getAlignment();
686 dataset = new Alignment(new SequenceI[] {});
689 Map<String, FeatureColourI> featureColours = new HashMap<>();
690 boolean parseResult = parse(dataset, featureColours, false, true);
693 // pass error up somehow
697 // update viewport with the dataset data ?
701 setSeqs(dataset.getSequencesArray());
706 * Implementation of unused abstract method
708 * @return error message
711 public String print(SequenceI[] sqs, boolean jvsuffix)
713 System.out.println("Use printGffFormat() or printJalviewFormat()");
718 * Returns features output in GFF2 format
721 * the sequences whose features are to be output
723 * a map whose keys are the type names of visible features
724 * @param visibleFeatureGroups
725 * @param includeNonPositionalFeatures
728 public String printGffFormat(SequenceI[] sequences,
729 Map<String, FeatureColourI> visible,
730 List<String> visibleFeatureGroups,
731 boolean includeNonPositionalFeatures)
733 StringBuilder out = new StringBuilder(256);
735 out.append(String.format("%s %d\n", GFF_VERSION, gffVersion == 0 ? 2 : gffVersion));
737 if (!includeNonPositionalFeatures
738 && (visible == null || visible.isEmpty()))
740 return out.toString();
743 String[] types = visible == null ? new String[0] : visible.keySet()
745 new String[visible.keySet().size()]);
747 for (SequenceI seq : sequences)
749 List<SequenceFeature> features = new ArrayList<>();
750 if (includeNonPositionalFeatures)
752 features.addAll(seq.getFeatures().getNonPositionalFeatures());
754 if (visible != null && !visible.isEmpty())
756 features.addAll(seq.getFeatures().getPositionalFeatures(types));
759 for (SequenceFeature sf : features)
761 String source = sf.featureGroup;
762 if (!sf.isNonPositional() && source != null
763 && !visibleFeatureGroups.contains(source))
765 // group is not visible
771 source = sf.getDescription();
774 out.append(seq.getName());
780 out.append(sf.begin);
784 out.append(sf.score);
787 int strand = sf.getStrand();
788 out.append(strand == 1 ? "+" : (strand == -1 ? "-" : "."));
791 String phase = sf.getPhase();
792 out.append(phase == null ? "." : phase);
794 // miscellaneous key-values (GFF column 9)
795 String attributes = sf.getAttributes();
796 if (attributes != null)
798 out.append(TAB).append(attributes);
805 return out.toString();
809 * Returns a mapping given list of one or more Align descriptors (exonerate
812 * @param alignedRegions
813 * a list of "Align fromStart toStart fromCount"
814 * @param mapIsFromCdna
815 * if true, 'from' is dna, else 'from' is protein
817 * either 1 (forward) or -1 (reverse)
819 * @throws IOException
821 protected MapList constructCodonMappingFromAlign(
822 List<String> alignedRegions, boolean mapIsFromCdna, int strand)
827 throw new IOException(
828 "Invalid strand for a codon mapping (cannot be 0)");
830 int regions = alignedRegions.size();
831 // arrays to hold [start, end] for each aligned region
832 int[] fromRanges = new int[regions * 2]; // from dna
833 int[] toRanges = new int[regions * 2]; // to protein
834 int fromRangesIndex = 0;
835 int toRangesIndex = 0;
837 for (String range : alignedRegions)
840 * Align mapFromStart mapToStart mapFromCount
841 * e.g. if mapIsFromCdna
842 * Align 11270 143 120
844 * 120 bases from pos 11270 align to pos 143 in peptide
845 * if !mapIsFromCdna this would instead be
848 String[] tokens = range.split(" ");
849 if (tokens.length != 3)
851 throw new IOException("Wrong number of fields for Align");
858 fromStart = Integer.parseInt(tokens[0]);
859 toStart = Integer.parseInt(tokens[1]);
860 fromCount = Integer.parseInt(tokens[2]);
861 } catch (NumberFormatException nfe)
863 throw new IOException(
864 "Invalid number in Align field: " + nfe.getMessage());
868 * Jalview always models from dna to protein, so adjust values if the
869 * GFF mapping is from protein to dna
874 int temp = fromStart;
878 fromRanges[fromRangesIndex++] = fromStart;
879 fromRanges[fromRangesIndex++] = fromStart + strand * (fromCount - 1);
882 * If a codon has an intron gap, there will be contiguous 'toRanges';
883 * this is handled for us by the MapList constructor.
884 * (It is not clear that exonerate ever generates this case)
886 toRanges[toRangesIndex++] = toStart;
887 toRanges[toRangesIndex++] = toStart + (fromCount - 1) / 3;
890 return new MapList(fromRanges, toRanges, 3, 1);
894 * Parse a GFF format feature. This may include creating a 'dummy' sequence to
895 * hold the feature, or for its mapped sequence, or both, to be resolved
896 * either later in the GFF file (##FASTA section), or when the user loads
897 * additional sequences.
901 * @param relaxedIdMatching
905 protected SequenceI parseGff(String[] gffColumns, AlignmentI alignment,
906 boolean relaxedIdMatching, List<SequenceI> newseqs)
909 * GFF: seqid source type start end score strand phase [attributes]
911 if (gffColumns.length < 5)
913 System.err.println("Ignoring GFF feature line with too few columns ("
914 + gffColumns.length + ")");
919 * locate referenced sequence in alignment _or_
920 * as a forward or external reference (SequenceDummy)
922 String seqId = gffColumns[0];
923 SequenceI seq = findSequence(seqId, alignment, newseqs,
926 SequenceFeature sf = null;
927 GffHelperI helper = GffHelperFactory.getHelper(gffColumns);
932 sf = helper.processGff(seq, gffColumns, alignment, newseqs,
936 seq.addSequenceFeature(sf);
937 while ((seq = alignment.findName(seq, seqId, true)) != null)
939 seq.addSequenceFeature(new SequenceFeature(sf));
942 } catch (IOException e)
944 System.err.println("GFF parsing failed with: " + e.getMessage());
953 * Process the 'column 9' data of the GFF file. This is less formally defined,
954 * and its interpretation will vary depending on the tool that has generated
960 protected void processGffColumnNine(String attributes, SequenceFeature sf)
962 sf.setAttributes(attributes);
965 * Parse attributes in column 9 and add them to the sequence feature's
966 * 'otherData' table; use Note as a best proxy for description
968 char nameValueSeparator = gffVersion == 3 ? '=' : ' ';
969 // TODO check we don't break GFF2 values which include commas here
970 Map<String, List<String>> nameValues = GffHelperBase
971 .parseNameValuePairs(attributes, ";", nameValueSeparator, ",");
972 for (Entry<String, List<String>> attr : nameValues.entrySet())
974 String values = StringUtils.listToDelimitedString(attr.getValue(),
976 sf.setValue(attr.getKey(), values);
977 if (NOTE.equals(attr.getKey()))
979 sf.setDescription(values);
985 * After encountering ##fasta in a GFF3 file, process the remainder of the
986 * file as FAST sequence data. Any placeholder sequences created during
987 * feature parsing are updated with the actual sequences.
991 * @throws IOException
993 protected void processAsFasta(AlignmentI align, List<SequenceI> newseqs)
999 } catch (IOException q)
1002 FastaFile parser = new FastaFile(this);
1003 List<SequenceI> includedseqs = parser.getSeqs();
1005 SequenceIdMatcher smatcher = new SequenceIdMatcher(newseqs);
1008 * iterate over includedseqs, and replacing matching ones with newseqs
1009 * sequences. Generic iterator not used here because we modify
1010 * includedseqs as we go
1012 for (int p = 0, pSize = includedseqs.size(); p < pSize; p++)
1014 // search for any dummy seqs that this sequence can be used to update
1015 SequenceI includedSeq = includedseqs.get(p);
1016 SequenceI dummyseq = smatcher.findIdMatch(includedSeq);
1017 if (dummyseq != null && dummyseq instanceof SequenceDummy)
1019 // probably have the pattern wrong
1020 // idea is that a flyweight proxy for a sequence ID can be created for
1021 // 1. stable reference creation
1022 // 2. addition of annotation
1023 // 3. future replacement by a real sequence
1024 // current pattern is to create SequenceDummy objects - a convenience
1025 // constructor for a Sequence.
1026 // problem is that when promoted to a real sequence, all references
1027 // need to be updated somehow. We avoid that by keeping the same object.
1028 ((SequenceDummy) dummyseq).become(includedSeq);
1029 dummyseq.createDatasetSequence();
1032 * Update mappings so they are now to the dataset sequence
1034 for (AlignedCodonFrame mapping : align.getCodonFrames())
1036 mapping.updateToDataset(dummyseq);
1040 * replace parsed sequence with the realised forward reference
1042 includedseqs.set(p, dummyseq);
1045 * and remove from the newseqs list
1047 newseqs.remove(dummyseq);
1052 * finally add sequences to the dataset
1054 for (SequenceI seq : includedseqs)
1056 // experimental: mapping-based 'alignment' to query sequence
1057 AlignmentUtils.alignSequenceAs(seq, align,
1058 String.valueOf(align.getGapCharacter()), false, true);
1060 // rename sequences if GFF handler requested this
1061 // TODO a more elegant way e.g. gffHelper.postProcess(newseqs) ?
1062 List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures();
1065 String newName = (String) sfs.get(0).getValue(
1066 GffHelperI.RENAME_TOKEN);
1067 if (newName != null)
1069 seq.setName(newName);
1072 align.addSequence(seq);
1077 * Process a ## directive
1083 * @throws IOException
1085 protected void processGffPragma(String line, Map<String, String> gffProps,
1086 AlignmentI align, List<SequenceI> newseqs) throws IOException
1089 if ("###".equals(line))
1091 // close off any open 'forward references'
1095 String[] tokens = line.substring(2).split(" ");
1096 String pragma = tokens[0];
1097 String value = tokens.length == 1 ? null : tokens[1];
1099 if ("gff-version".equalsIgnoreCase(pragma))
1105 // value may be e.g. "3.1.2"
1106 gffVersion = Integer.parseInt(value.split("\\.")[0]);
1107 } catch (NumberFormatException e)
1113 else if ("sequence-region".equalsIgnoreCase(pragma))
1115 // could capture <seqid start end> if wanted here
1117 else if ("feature-ontology".equalsIgnoreCase(pragma))
1119 // should resolve against the specified feature ontology URI
1121 else if ("attribute-ontology".equalsIgnoreCase(pragma))
1123 // URI of attribute ontology - not currently used in GFF3
1125 else if ("source-ontology".equalsIgnoreCase(pragma))
1127 // URI of source ontology - not currently used in GFF3
1129 else if ("species-build".equalsIgnoreCase(pragma))
1131 // save URI of specific NCBI taxon version of annotations
1132 gffProps.put("species-build", value);
1134 else if ("fasta".equalsIgnoreCase(pragma))
1136 // process the rest of the file as a fasta file and replace any dummy
1138 processAsFasta(align, newseqs);
1142 System.err.println("Ignoring unknown pragma: " + line);