From 6fb128aa10308c922ae36c9a954e6a91c77618df Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Sun, 13 Apr 2025 18:09:13 +0200 Subject: [PATCH 1/7] abstract gxx --- .../htsjdk/tribble/gff/AbstractGxxCodec.java | 221 +++++++ .../java/htsjdk/tribble/gff/Gff3Codec.java | 176 +----- .../java/htsjdk/tribble/gff/GtfCodec.java | 586 ++++++++++++++++++ 3 files changed, 840 insertions(+), 143 deletions(-) create mode 100644 src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java create mode 100644 src/main/java/htsjdk/tribble/gff/GtfCodec.java diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java new file mode 100644 index 0000000000..2c44aa47ae --- /dev/null +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java @@ -0,0 +1,221 @@ +package htsjdk.tribble.gff; + +import htsjdk.samtools.util.CloserUtil; +import htsjdk.samtools.util.FileExtensions; +import htsjdk.samtools.util.IOUtil; +import htsjdk.samtools.util.LocationAware; + +import htsjdk.samtools.util.Log; +import htsjdk.tribble.AbstractFeatureCodec; +import htsjdk.tribble.Feature; +import htsjdk.tribble.FeatureCodecHeader; +import htsjdk.tribble.TribbleException; +import htsjdk.tribble.annotation.Strand; +import htsjdk.tribble.index.tabix.TabixFormat; +import htsjdk.tribble.readers.*; +import htsjdk.tribble.util.ParsingUtils; + + + +import java.io.*; +import java.net.URLDecoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + +/** + * Abstract Base Codec for parsing Gff3 files or GTF codec + */ + +public abstract class AbstractGxxCodec extends AbstractFeatureCodec { + + protected static final int NUM_FIELDS = 9; + + protected static final int CHROMOSOME_NAME_INDEX = 0; + protected static final int ANNOTATION_SOURCE_INDEX = 1; + protected static final int FEATURE_TYPE_INDEX = 2; + protected static final int START_LOCATION_INDEX = 3; + protected static final int END_LOCATION_INDEX = 4; + protected static final int SCORE_INDEX = 5; + protected static final int GENOMIC_STRAND_INDEX = 6; + protected static final int GENOMIC_PHASE_INDEX = 7; + protected static final int EXTRA_FIELDS_INDEX = 8; + + + protected final Queue activeFeatures = new ArrayDeque<>(); + protected final Queue featuresToFlush = new ArrayDeque<>(); + protected final Map> activeFeaturesWithIDs = new HashMap<>(); + protected final Map> activeParentIDs = new HashMap<>(); + protected final Map commentsWithLineNumbers = new LinkedHashMap<>(); + + + protected DecodeDepth decodeDepth; + + protected int currentLine = 0; + + /** filter to removing keys from the EXTRA_FIELDS column */ + protected final Predicate filterOutAttribute; + + + + /** + * @param decodeDepth a value from DecodeDepth + * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column + */ + protected AbstractGxxCodec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { + super(Gff3Feature.class); + this.decodeDepth = decodeDepth; + this.filterOutAttribute = filterOutAttribute; + } + + + public enum DecodeDepth { + DEEP , + SHALLOW + } + + @Override + public final Gff3Feature decode(final LineIterator lineIterator) throws IOException { + return decode(lineIterator, decodeDepth); + } + + protected abstract Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) throws IOException; + + + + /** + * Parse attributes field for gff3 feature + * @param attributesString attributes field string from line in gff3 file + * @return map of keys to values for attributes of this feature + * @throws UnsupportedEncodingException + */ + protected abstract Map> parseAttributesColumn(final String attributesString) throws UnsupportedEncodingException; + + + protected final Gff3BaseData parseLine(final String line, final int currentLine, final Predicate filterOutAttribute) { + final List splitLine = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); + + if (splitLine.size() != NUM_FIELDS) { + throw new TribbleException("Found an invalid number of columns in the given Gff3/GTF file at line + " + currentLine + " - Given: " + splitLine.size() + " Expected: " + NUM_FIELDS + " : " + line); + } + + try { + final String contig = URLDecoder.decode(splitLine.get(CHROMOSOME_NAME_INDEX), "UTF-8"); + final String source = URLDecoder.decode(splitLine.get(ANNOTATION_SOURCE_INDEX), "UTF-8"); + final String type = URLDecoder.decode(splitLine.get(FEATURE_TYPE_INDEX), "UTF-8"); + final int start = Integer.parseInt(splitLine.get(START_LOCATION_INDEX)); + final int end = Integer.parseInt(splitLine.get(END_LOCATION_INDEX)); + final double score = splitLine.get(SCORE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Double.parseDouble(splitLine.get(SCORE_INDEX)); + final int phase = splitLine.get(GENOMIC_PHASE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); + final Strand strand = Strand.decode(splitLine.get(GENOMIC_STRAND_INDEX)); + final Map> attributes = parseAttributesColumn(splitLine.get(EXTRA_FIELDS_INDEX)); + /* remove attibutes matching 'filterOutAttribute' */ + attributes.keySet().removeIf(filterOutAttribute); + return new Gff3BaseData(contig, source, type, start, end, score, strand, phase, attributes); + } catch (final NumberFormatException ex ) { + throw new TribbleException("Cannot read integer value for start/end position from line " + currentLine + ". Line is: " + line, ex); + } catch (final IOException ex) { + throw new TribbleException("Cannot decode feature info from line " + currentLine + ". Line is: " + line, ex); + } + } + + /** + * Gets map from line number to comment found on that line. The text of the comment EXCLUDES the leading # which indicates a comment line. + * @return Map from line number to comment found on line + */ + public final Map getCommentsWithLineNumbers() { + return Collections.unmodifiableMap(new LinkedHashMap<>(commentsWithLineNumbers)); + } + + /** + * Gets list of comments parsed by the codec. Excludes leading # which indicates a comment line. + * @return + */ + public final List getCommentTexts() { + return Collections.unmodifiableList(new ArrayList<>(commentsWithLineNumbers.values())); + } + + @Override + public final Feature decodeLoc(LineIterator lineIterator) throws IOException { + return decode(lineIterator, DecodeDepth.SHALLOW); + } + + @Override + public abstract boolean canDecode(final String inputFilePath); + + static List decodeAttributeValue(final String attributeValue) { + //split on VALUE_DELIMITER, then decode + final List splitValues = ParsingUtils.split(attributeValue, Gff3Constants.VALUE_DELIMITER); + + final List decodedValues = new ArrayList<>(); + for (final String encodedValue : splitValues) { + try { + decodedValues.add(URLDecoder.decode(encodedValue.trim(), "UTF-8")); + } catch (final UnsupportedEncodingException ex) { + throw new TribbleException("Error decoding attribute " + encodedValue, ex); + } + } + + return decodedValues; + } + + static String extractSingleAttribute(final List values) { + if (values == null || values.isEmpty()) { + return null; + } + + if (values.size() != 1) { + throw new TribbleException("Attribute has multiple values when only one expected"); + } + return values.get(0); + } + + @Override + public final FeatureCodecHeader readHeader(LineIterator lineIterator) { + + List header = new ArrayList<>(); + while(lineIterator.hasNext()) { + String line = lineIterator.peek(); + if (line.startsWith(Gff3Constants.COMMENT_START)) { + header.add(line); + lineIterator.next(); + } else { + break; + } + } + + return new FeatureCodecHeader(header, FeatureCodecHeader.NO_HEADER_END); + } + + @Override + public final LineIterator makeSourceFromStream(final InputStream bufferedInputStream) { + return new LineIteratorImpl(new SynchronousLineReader(bufferedInputStream)); + } + + @Override + public final LocationAware makeIndexableSourceFromStream(final InputStream bufferedInputStream) { + return new AsciiLineReaderIterator(AsciiLineReader.from(bufferedInputStream)); + } + + @Override + public abstract boolean isDone(final LineIterator lineIterator); + + @Override + public void close(final LineIterator lineIterator) { + //cleanup resources + featuresToFlush.clear(); + activeFeaturesWithIDs.clear(); + activeFeatures.clear(); + activeParentIDs.clear(); + CloserUtil.close(lineIterator); + } + + @Override + public final TabixFormat getTabixFormat() { + return TabixFormat.GFF; + } + +} diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java index 6bf5be284c..0e9250e339 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java @@ -1,31 +1,34 @@ package htsjdk.tribble.gff; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.FileExtensions; import htsjdk.samtools.util.IOUtil; -import htsjdk.samtools.util.LocationAware; - import htsjdk.samtools.util.Log; -import htsjdk.tribble.AbstractFeatureCodec; -import htsjdk.tribble.Feature; -import htsjdk.tribble.FeatureCodecHeader; import htsjdk.tribble.TribbleException; import htsjdk.tribble.annotation.Strand; -import htsjdk.tribble.index.tabix.TabixFormat; -import htsjdk.tribble.readers.*; +import htsjdk.tribble.readers.LineIterator; import htsjdk.tribble.util.ParsingUtils; - - -import java.io.*; -import java.net.URLDecoder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; - /** * Codec for parsing Gff3 files, as defined in https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md * Note that while spec states that all feature types must be defined in sequence ontology, this implementation makes no check on feature types, and allows any string as feature type @@ -35,45 +38,17 @@ * region, using a tribble index. In this case, a particular feature will only be linked to the subgroup of features it is linked to in the input file which overlap the given region. */ -public class Gff3Codec extends AbstractFeatureCodec { - - - - private static final int NUM_FIELDS = 9; - - private static final int CHROMOSOME_NAME_INDEX = 0; - private static final int ANNOTATION_SOURCE_INDEX = 1; - private static final int FEATURE_TYPE_INDEX = 2; - private static final int START_LOCATION_INDEX = 3; - private static final int END_LOCATION_INDEX = 4; - private static final int SCORE_INDEX = 5; - private static final int GENOMIC_STRAND_INDEX = 6; - private static final int GENOMIC_PHASE_INDEX = 7; - private static final int EXTRA_FIELDS_INDEX = 8; - +public class Gff3Codec extends AbstractGxxCodec { private static final String IS_CIRCULAR_ATTRIBUTE_KEY = "Is_circular"; private static final String ARTEMIS_FASTA_MARKER = ">"; - private final Queue activeFeatures = new ArrayDeque<>(); - private final Queue featuresToFlush = new ArrayDeque<>(); - private final Map> activeFeaturesWithIDs = new HashMap<>(); - private final Map> activeParentIDs = new HashMap<>(); - private final Map sequenceRegionMap = new LinkedHashMap<>(); - private final Map commentsWithLineNumbers = new LinkedHashMap<>(); private final static Log logger = Log.getInstance(Gff3Codec.class); private boolean reachedFasta = false; - - private DecodeDepth decodeDepth; - - private int currentLine = 0; - - /** filter to removing keys from the EXTRA_FIELDS column */ - private final Predicate filterOutAttribute; public Gff3Codec() { this(DecodeDepth.DEEP); @@ -88,9 +63,7 @@ public Gff3Codec(final DecodeDepth decodeDepth) { * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column */ public Gff3Codec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { - super(Gff3Feature.class); - this.decodeDepth = decodeDepth; - this.filterOutAttribute = filterOutAttribute; + super(decodeDepth,filterOutAttribute); /* check required keys are always kept */ for (final String key : new String[] {Gff3Constants.PARENT_ATTRIBUTE_KEY, Gff3Constants.ID_ATTRIBUTE_KEY, Gff3Constants.NAME_ATTRIBUTE_KEY}) { if (filterOutAttribute.test(key)) { @@ -99,17 +72,8 @@ public Gff3Codec(final DecodeDepth decodeDepth, final Predicate filterOu } } - public enum DecodeDepth { - DEEP , - SHALLOW - } - @Override - public Gff3Feature decode(final LineIterator lineIterator) throws IOException { - return decode(lineIterator, decodeDepth); - } - - private Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) throws IOException { + protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) throws IOException { currentLine++; /* Basic strategy: Load features into deque, create maps from a features ID to it, and from a features parents' IDs to it. For each feature, link to parents using these maps. @@ -147,8 +111,7 @@ private Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth de } - - final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, this.filterOutAttribute)); + final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, super.filterOutAttribute)); activeFeatures.add(thisFeature); if (depth == DecodeDepth.DEEP) { //link to parents/children/co-features @@ -196,7 +159,11 @@ private Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth de return featuresToFlush.poll(); } - + @Override + protected Map> parseAttributesColumn(String attributesString) + throws UnsupportedEncodingException { + return parseAttributes(attributesString); + } /** * Parse attributes field for gff3 feature * @param attributesString attributes field string from line in gff3 file @@ -219,33 +186,6 @@ static private Map> parseAttributes(final String attributes return attributes; } - private static Gff3BaseData parseLine(final String line, final int currentLine, final Predicate filterOutAttribute) { - final List splitLine = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); - - if (splitLine.size() != NUM_FIELDS) { - throw new TribbleException("Found an invalid number of columns in the given Gff3 file at line + " + currentLine + " - Given: " + splitLine.size() + " Expected: " + NUM_FIELDS + " : " + line); - } - - try { - final String contig = URLDecoder.decode(splitLine.get(CHROMOSOME_NAME_INDEX), "UTF-8"); - final String source = URLDecoder.decode(splitLine.get(ANNOTATION_SOURCE_INDEX), "UTF-8"); - final String type = URLDecoder.decode(splitLine.get(FEATURE_TYPE_INDEX), "UTF-8"); - final int start = Integer.parseInt(splitLine.get(START_LOCATION_INDEX)); - final int end = Integer.parseInt(splitLine.get(END_LOCATION_INDEX)); - final double score = splitLine.get(SCORE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Double.parseDouble(splitLine.get(SCORE_INDEX)); - final int phase = splitLine.get(GENOMIC_PHASE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); - final Strand strand = Strand.decode(splitLine.get(GENOMIC_STRAND_INDEX)); - final Map> attributes = parseAttributes(splitLine.get(EXTRA_FIELDS_INDEX)); - /* remove attibutes matching 'filterOutAttribute' */ - attributes.keySet().removeIf(filterOutAttribute); - return new Gff3BaseData(contig, source, type, start, end, score, strand, phase, attributes); - } catch (final NumberFormatException ex ) { - throw new TribbleException("Cannot read integer value for start/end position from line " + currentLine + ". Line is: " + line, ex); - } catch (final IOException ex) { - throw new TribbleException("Cannot decode feature info from line " + currentLine + ". Line is: " + line, ex); - } - } - /** * Get list of sequence regions parsed by the codec. * @return list of sequence regions @@ -254,21 +194,6 @@ public List getSequenceRegions() { return Collections.unmodifiableList(new ArrayList<>(sequenceRegionMap.values())); } - /** - * Gets map from line number to comment found on that line. The text of the comment EXCLUDES the leading # which indicates a comment line. - * @return Map from line number to comment found on line - */ - public Map getCommentsWithLineNumbers() { - return Collections.unmodifiableMap(new LinkedHashMap<>(commentsWithLineNumbers)); - } - - /** - * Gets list of comments parsed by the codec. Excludes leading # which indicates a comment line. - * @return - */ - public List getCommentTexts() { - return Collections.unmodifiableList(new ArrayList<>(commentsWithLineNumbers.values())); - } /** * If sequence region of feature's contig has been specified with sequence region directive, validates that @@ -290,10 +215,6 @@ private void validateFeature(final Gff3Feature feature) { } } - @Override - public Feature decodeLoc(LineIterator lineIterator) throws IOException { - return decode(lineIterator, DecodeDepth.SHALLOW); - } @Override public boolean canDecode(final String inputFilePath) { @@ -331,8 +252,8 @@ public boolean canDecode(final String inputFilePath) { if (canDecode) { // check that start and end fields are integers try { - final int start = Integer.parseInt(fields.get(3)); - final int end = Integer.parseInt(fields.get(4)); + /* final int start = */ Integer.parseInt(fields.get(3)); + /* final int end = */ Integer.parseInt(fields.get(4)); } catch (NumberFormatException | NullPointerException nfe) { return false; } @@ -349,7 +270,7 @@ public boolean canDecode(final String inputFilePath) { } } - catch (FileNotFoundException ex) { + catch (final FileNotFoundException ex) { logger.error(inputFilePath + " not found."); return false; } @@ -387,22 +308,6 @@ static String extractSingleAttribute(final List values) { return values.get(0); } - @Override - public FeatureCodecHeader readHeader(LineIterator lineIterator) { - - List header = new ArrayList<>(); - while(lineIterator.hasNext()) { - String line = lineIterator.peek(); - if (line.startsWith(Gff3Constants.COMMENT_START)) { - header.add(line); - lineIterator.next(); - } else { - break; - } - } - - return new FeatureCodecHeader(header, FeatureCodecHeader.NO_HEADER_END); - } /** * Parse a directive line from a gff3 file @@ -461,15 +366,6 @@ private void prepareToFlushFeatures() { activeParentIDs.clear(); } - @Override - public LineIterator makeSourceFromStream(final InputStream bufferedInputStream) { - return new LineIteratorImpl(new SynchronousLineReader(bufferedInputStream)); - } - - @Override - public LocationAware makeIndexableSourceFromStream(final InputStream bufferedInputStream) { - return new AsciiLineReaderIterator(AsciiLineReader.from(bufferedInputStream)); - } @Override public boolean isDone(final LineIterator lineIterator) { @@ -486,11 +382,6 @@ public void close(final LineIterator lineIterator) { CloserUtil.close(lineIterator); } - @Override - public TabixFormat getTabixFormat() { - return TabixFormat.GFF; - } - /** * Enum for parsing directive lines. If information in directive line needs to be parsed beyond specifying directive type, decode method should be overriden */ @@ -583,5 +474,4 @@ protected Object decode(final String line) throws IOException { abstract String encode(final Object object); } - } diff --git a/src/main/java/htsjdk/tribble/gff/GtfCodec.java b/src/main/java/htsjdk/tribble/gff/GtfCodec.java new file mode 100644 index 0000000000..38dea06cb8 --- /dev/null +++ b/src/main/java/htsjdk/tribble/gff/GtfCodec.java @@ -0,0 +1,586 @@ +package htsjdk.tribble.gff; + +import htsjdk.samtools.util.CloserUtil; +import htsjdk.samtools.util.FileExtensions; +import htsjdk.samtools.util.IOUtil; +import htsjdk.samtools.util.LocationAware; + +import htsjdk.samtools.util.Log; +import htsjdk.tribble.AbstractFeatureCodec; +import htsjdk.tribble.Feature; +import htsjdk.tribble.FeatureCodecHeader; +import htsjdk.tribble.TribbleException; +import htsjdk.tribble.annotation.Strand; +import htsjdk.tribble.index.tabix.TabixFormat; +import htsjdk.tribble.readers.*; +import htsjdk.tribble.util.ParsingUtils; + + + +import java.io.*; +import java.net.URLDecoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + +/** + * Codec for parsing Gff3 files, as defined in https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md + * Note that while spec states that all feature types must be defined in sequence ontology, this implementation makes no check on feature types, and allows any string as feature type + * + * Each feature line in the Gff3 file will be emitted as a separate feature. Features linked together through the "Parent" attribute will be linked through {@link Gff3Feature#getParents()}, {@link Gff3Feature#getChildren()}, + * {@link Gff3Feature#getAncestors()}, {@link Gff3Feature#getDescendents()}, amd {@link Gff3Feature#flatten()}. This linking is not guaranteed to be comprehensive when the file is read for only features overlapping a particular + * region, using a tribble index. In this case, a particular feature will only be linked to the subgroup of features it is linked to in the input file which overlap the given region. + */ + +public class GtfCodec extends AbstractFeatureCodec { + + + private static final int NUM_FIELDS = 9; + + private static final int CHROMOSOME_NAME_INDEX = 0; + private static final int ANNOTATION_SOURCE_INDEX = 1; + private static final int FEATURE_TYPE_INDEX = 2; + private static final int START_LOCATION_INDEX = 3; + private static final int END_LOCATION_INDEX = 4; + private static final int SCORE_INDEX = 5; + private static final int GENOMIC_STRAND_INDEX = 6; + private static final int GENOMIC_PHASE_INDEX = 7; + private static final int EXTRA_FIELDS_INDEX = 8; + + + private static final String IS_CIRCULAR_ATTRIBUTE_KEY = "Is_circular"; + + private static final String ARTEMIS_FASTA_MARKER = ">"; + + private final Queue activeFeatures = new ArrayDeque<>(); + private final Queue featuresToFlush = new ArrayDeque<>(); + private final Map> activeFeaturesWithIDs = new HashMap<>(); + private final Map> activeParentIDs = new HashMap<>(); + + private final Map sequenceRegionMap = new LinkedHashMap<>(); + private final Map commentsWithLineNumbers = new LinkedHashMap<>(); + + private final static Log logger = Log.getInstance(Gff3Codec.class); + + private boolean reachedFasta = false; + + private DecodeDepth decodeDepth; + + private int currentLine = 0; + + /** filter to removing keys from the EXTRA_FIELDS column */ + private final Predicate filterOutAttribute; + + public Gff3Codec() { + this(DecodeDepth.DEEP); + } + + public Gff3Codec(final DecodeDepth decodeDepth) { + this(decodeDepth, KEY -> false); + } + + /** + * @param decodeDepth a value from DecodeDepth + * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column + */ + public Gff3Codec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { + super(Gff3Feature.class); + this.decodeDepth = decodeDepth; + this.filterOutAttribute = filterOutAttribute; + /* check required keys are always kept */ + for (final String key : new String[] {Gff3Constants.PARENT_ATTRIBUTE_KEY, Gff3Constants.ID_ATTRIBUTE_KEY, Gff3Constants.NAME_ATTRIBUTE_KEY}) { + if (filterOutAttribute.test(key)) { + throw new IllegalArgumentException("Predicate should always accept " + key); + } + } + } + + public enum DecodeDepth { + DEEP , + SHALLOW + } + + @Override + public Gff3Feature decode(final LineIterator lineIterator) throws IOException { + return decode(lineIterator, decodeDepth); + } + + private Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) throws IOException { + currentLine++; + /* + Basic strategy: Load features into deque, create maps from a features ID to it, and from a features parents' IDs to it. For each feature, link to parents using these maps. + When reaching flush directive, fasta, or end of file, prepare to flush features by moving all active features to deque of features to flush, and clearing + list of active features and both maps. Always poll featuresToFlush to return any completed top level features. + */ + if (!lineIterator.hasNext()) { + //no more lines, flush whatever is active + prepareToFlushFeatures(); + return featuresToFlush.poll(); + } + + final String line = lineIterator.next(); + + if (reachedFasta) { + //previously reached fasta, flush whatever is active + prepareToFlushFeatures(); + return featuresToFlush.poll(); + } + + if (line.startsWith(ARTEMIS_FASTA_MARKER)) { + //backwards compatability with Artemis is built into gff3 spec + processDirective(Gff3Directive.FASTA_DIRECTIVE, null); + return featuresToFlush.poll(); + } + + if (line.startsWith(Gff3Constants.COMMENT_START) && !line.startsWith(Gff3Constants.DIRECTIVE_START)) { + commentsWithLineNumbers.put(currentLine, line.substring(Gff3Constants.COMMENT_START.length())); + return featuresToFlush.poll(); + } + + if (line.startsWith(Gff3Constants.DIRECTIVE_START)) { + parseDirective(line); + return featuresToFlush.poll(); + } + + + + final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, this.filterOutAttribute)); + activeFeatures.add(thisFeature); + if (depth == DecodeDepth.DEEP) { + //link to parents/children/co-features + final List parentIDs = thisFeature.getAttribute(Gff3Constants.PARENT_ATTRIBUTE_KEY); + final String id = thisFeature.getID(); + + for (final String parentID : parentIDs) { + + final Set theseParents = activeFeaturesWithIDs.get(parentID); + if (theseParents != null) { + for (final Gff3FeatureImpl parent : theseParents) { + thisFeature.addParent(parent); + } + } + if (activeParentIDs.containsKey(parentID)) { + activeParentIDs.get(parentID).add(thisFeature); + } else { + activeParentIDs.put(parentID, new HashSet<>(Collections.singleton(thisFeature))); + } + } + + if (id != null) { + if (activeFeaturesWithIDs.containsKey(id)) { + for (final Gff3FeatureImpl coFeature : activeFeaturesWithIDs.get(id)) { + thisFeature.addCoFeature(coFeature); + } + activeFeaturesWithIDs.get(id).add(thisFeature); + } else { + activeFeaturesWithIDs.put(id, new HashSet<>(Collections.singleton(thisFeature))); + } + } + + if (activeParentIDs.containsKey(thisFeature.getID())) { + for (final Gff3FeatureImpl child : activeParentIDs.get(thisFeature.getID())) { + child.addParent(thisFeature); + } + } + } + + validateFeature(thisFeature); + if (depth == DecodeDepth.SHALLOW) { + //flush all features immediatly + prepareToFlushFeatures(); + } + return featuresToFlush.poll(); + } + + + /** + * Parse attributes field for gff3 feature + * @param attributesString attributes field string from line in gff3 file + * @return map of keys to values for attributes of this feature + * @throws UnsupportedEncodingException + */ + static private Map> parseAttributes(final String attributesString) throws UnsupportedEncodingException { + if (attributesString.equals(Gff3Constants.UNDEFINED_FIELD_VALUE)) { + return Collections.emptyMap(); + } + final Map> attributes = new LinkedHashMap<>(); + final List splitLine = ParsingUtils.split(attributesString,Gff3Constants.ATTRIBUTE_DELIMITER); + for(String attribute : splitLine) { + final List key_value = ParsingUtils.split(attribute,Gff3Constants.KEY_VALUE_SEPARATOR); + if (key_value.size() != 2) { + throw new TribbleException("Attribute string " + attributesString + " is invalid"); + } + attributes.put(URLDecoder.decode(key_value.get(0).trim(), "UTF-8"), decodeAttributeValue(key_value.get(1).trim())); + } + return attributes; + } + + private static Gff3BaseData parseLine(final String line, final int currentLine, final Predicate filterOutAttribute) { + final List splitLine = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); + + if (splitLine.size() != NUM_FIELDS) { + throw new TribbleException("Found an invalid number of columns in the given Gff3 file at line + " + currentLine + " - Given: " + splitLine.size() + " Expected: " + NUM_FIELDS + " : " + line); + } + + try { + final String contig = URLDecoder.decode(splitLine.get(CHROMOSOME_NAME_INDEX), "UTF-8"); + final String source = URLDecoder.decode(splitLine.get(ANNOTATION_SOURCE_INDEX), "UTF-8"); + final String type = URLDecoder.decode(splitLine.get(FEATURE_TYPE_INDEX), "UTF-8"); + final int start = Integer.parseInt(splitLine.get(START_LOCATION_INDEX)); + final int end = Integer.parseInt(splitLine.get(END_LOCATION_INDEX)); + final double score = splitLine.get(SCORE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Double.parseDouble(splitLine.get(SCORE_INDEX)); + final int phase = splitLine.get(GENOMIC_PHASE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); + final Strand strand = Strand.decode(splitLine.get(GENOMIC_STRAND_INDEX)); + final Map> attributes = parseAttributes(splitLine.get(EXTRA_FIELDS_INDEX)); + /* remove attibutes matching 'filterOutAttribute' */ + attributes.keySet().removeIf(filterOutAttribute); + return new Gff3BaseData(contig, source, type, start, end, score, strand, phase, attributes); + } catch (final NumberFormatException ex ) { + throw new TribbleException("Cannot read integer value for start/end position from line " + currentLine + ". Line is: " + line, ex); + } catch (final IOException ex) { + throw new TribbleException("Cannot decode feature info from line " + currentLine + ". Line is: " + line, ex); + } + } + + /** + * Get list of sequence regions parsed by the codec. + * @return list of sequence regions + */ + public List getSequenceRegions() { + return Collections.unmodifiableList(new ArrayList<>(sequenceRegionMap.values())); + } + + /** + * Gets map from line number to comment found on that line. The text of the comment EXCLUDES the leading # which indicates a comment line. + * @return Map from line number to comment found on line + */ + public Map getCommentsWithLineNumbers() { + return Collections.unmodifiableMap(new LinkedHashMap<>(commentsWithLineNumbers)); + } + + /** + * Gets list of comments parsed by the codec. Excludes leading # which indicates a comment line. + * @return + */ + public List getCommentTexts() { + return Collections.unmodifiableList(new ArrayList<>(commentsWithLineNumbers.values())); + } + + /** + * If sequence region of feature's contig has been specified with sequence region directive, validates that + * feature's coordinates are within the specified sequence region. TribbleException is thrown if invalid. + * @param feature + */ + private void validateFeature(final Gff3Feature feature) { + if (sequenceRegionMap.containsKey(feature.getContig())) { + final SequenceRegion region = sequenceRegionMap.get(feature.getContig()); + if (feature.getStart() == region.getStart() && feature.getEnd() == region.getEnd()) { + //landmark feature + final boolean isCircular = Boolean.parseBoolean(extractSingleAttribute(feature.getAttribute(IS_CIRCULAR_ATTRIBUTE_KEY))); + region.setCircular(isCircular); + } + if (region.isCircular()? !region.overlaps(feature) : !region.contains(feature)) { + throw new TribbleException("feature at " + feature.getContig() + ":" + feature.getStart() + "-" + feature.getEnd() + + " not contained in specified sequence region (" + region.getContig() + ":" + region.getStart() + "-" + region.getEnd()); + } + } + } + + @Override + public Feature decodeLoc(LineIterator lineIterator) throws IOException { + return decode(lineIterator, DecodeDepth.SHALLOW); + } + + @Override + public boolean canDecode(final String inputFilePath) { + boolean canDecode; + try { + // Simple file and name checks to start with: + Path p = IOUtil.getPath(inputFilePath); + canDecode = FileExtensions.GFF3.stream().anyMatch(fe -> p.toString().endsWith(fe)); + + if (canDecode) { + + // Crack open the file and look at the top of it: + final InputStream inputStream = IOUtil.hasGzipFileExtension(p)? new GZIPInputStream(Files.newInputStream(p)) : Files.newInputStream(p); + + try ( BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) ) { + + String line = br.readLine(); + + // First line must be GFF version directive + if (Gff3Directive.toDirective(line) != Gff3Directive.VERSION3_DIRECTIVE) { + return false; + } + while (line.startsWith(Gff3Constants.COMMENT_START)) { + line = br.readLine(); + if ( line == null ) { + return false; + } + } + + // make sure line conforms to gtf spec + final List fields = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); + + canDecode &= fields.size() == NUM_FIELDS; + + if (canDecode) { + // check that start and end fields are integers + try { + final int start = Integer.parseInt(fields.get(3)); + final int end = Integer.parseInt(fields.get(4)); + } catch (NumberFormatException | NullPointerException nfe) { + return false; + } + + // check for strand + + final String strand = fields.get(GENOMIC_STRAND_INDEX); + canDecode &= strand.equals(Strand.POSITIVE.toString()) || + strand.equals(Strand.NEGATIVE.toString()) || + strand.equals(Strand.NONE.toString()) || + strand.equals("?"); + } + } + + } + } + catch (FileNotFoundException ex) { + logger.error(inputFilePath + " not found."); + return false; + } + catch (final IOException ex) { + return false; + } + + return canDecode; + } + + static List decodeAttributeValue(final String attributeValue) { + //split on VALUE_DELIMITER, then decode + final List splitValues = ParsingUtils.split(attributeValue, Gff3Constants.VALUE_DELIMITER); + + final List decodedValues = new ArrayList<>(); + for (final String encodedValue : splitValues) { + try { + decodedValues.add(URLDecoder.decode(encodedValue.trim(), "UTF-8")); + } catch (final UnsupportedEncodingException ex) { + throw new TribbleException("Error decoding attribute " + encodedValue, ex); + } + } + + return decodedValues; + } + + static String extractSingleAttribute(final List values) { + if (values == null || values.isEmpty()) { + return null; + } + + if (values.size() != 1) { + throw new TribbleException("Attribute has multiple values when only one expected"); + } + return values.get(0); + } + + @Override + public FeatureCodecHeader readHeader(LineIterator lineIterator) { + + List header = new ArrayList<>(); + while(lineIterator.hasNext()) { + String line = lineIterator.peek(); + if (line.startsWith(Gff3Constants.COMMENT_START)) { + header.add(line); + lineIterator.next(); + } else { + break; + } + } + + return new FeatureCodecHeader(header, FeatureCodecHeader.NO_HEADER_END); + } + + /** + * Parse a directive line from a gff3 file + * @param directiveLine + * @throws IOException + */ + private void parseDirective(final String directiveLine) throws IOException { + final Gff3Directive directive = Gff3Directive.toDirective(directiveLine); + if (directive != null) { + processDirective(directive, directive.decode(directiveLine)); + + } else { + logger.warn("ignoring directive " + directiveLine); + } + } + + /** + * Process a gff3 directive + * @param directive the gff3 directive, indicated by a specific directive line + * @param decodedResult the decoding of the directive line by the given directive + */ + private void processDirective(final Gff3Directive directive, final Object decodedResult) { + switch (directive) { + case VERSION3_DIRECTIVE: + break; + + case SEQUENCE_REGION_DIRECTIVE: + final SequenceRegion newRegion = (SequenceRegion) decodedResult; + if (sequenceRegionMap.containsKey(newRegion.getContig())) { + throw new TribbleException("directive for sequence-region " + newRegion.getContig() + " included more than once."); + } + sequenceRegionMap.put(newRegion.getContig(), newRegion); + break; + + case FLUSH_DIRECTIVE: + prepareToFlushFeatures(); + break; + + case FASTA_DIRECTIVE: + reachedFasta = true; + break; + + default: + throw new IllegalArgumentException( "Directive " + directive + " has been added to Gff3Directive, but is not being handled by Gff3Codec::processDirective. This is a BUG."); + + } + } + + /** + * move active top level features to featuresToFlush. clear active features. + */ + private void prepareToFlushFeatures() { + featuresToFlush.addAll(activeFeatures); + activeFeaturesWithIDs.clear(); + activeFeatures.clear(); + activeParentIDs.clear(); + } + + @Override + public LineIterator makeSourceFromStream(final InputStream bufferedInputStream) { + return new LineIteratorImpl(new SynchronousLineReader(bufferedInputStream)); + } + + @Override + public LocationAware makeIndexableSourceFromStream(final InputStream bufferedInputStream) { + return new AsciiLineReaderIterator(AsciiLineReader.from(bufferedInputStream)); + } + + @Override + public boolean isDone(final LineIterator lineIterator) { + return !lineIterator.hasNext() && activeFeatures.isEmpty() && featuresToFlush.isEmpty(); + } + + @Override + public void close(final LineIterator lineIterator) { + //cleanup resources + featuresToFlush.clear(); + activeFeaturesWithIDs.clear(); + activeFeatures.clear(); + activeParentIDs.clear(); + CloserUtil.close(lineIterator); + } + + @Override + public TabixFormat getTabixFormat() { + return TabixFormat.GFF; + } + + /** + * Enum for parsing directive lines. If information in directive line needs to be parsed beyond specifying directive type, decode method should be overriden + */ + public enum Gff3Directive { + + VERSION3_DIRECTIVE("##gff-version\\s+3(?:\\.\\d*)*$") { + @Override + protected Object decode(final String line) throws IOException { + final String[] splitLine = line.split("\\s+"); + return splitLine[1]; + } + + @Override + String encode(final Object object) { + if (object == null) { + throw new TribbleException("Cannot encode null in VERSION3_DIRECTIVE"); + } + if (!(object instanceof String)) { + throw new TribbleException("Cannot encode object of type " + object.getClass() + " in VERSION3_DIRECTIVE"); + } + + final String versionLine = "##gff-version " + (String)object; + if (!regexPattern.matcher(versionLine).matches()) { + throw new TribbleException("Version " + (String)object + " is not a valid version"); + } + + return versionLine; + } + }, + + SEQUENCE_REGION_DIRECTIVE("##sequence-region\\s+.+ \\d+ \\d+$") { + final private int CONTIG_INDEX = 1; + final private int START_INDEX = 2; + final private int END_INDEX = 3; + @Override + protected Object decode(final String line) throws IOException { + final String[] splitLine = line.split("\\s+"); + final String contig = URLDecoder.decode(splitLine[CONTIG_INDEX], "UTF-8"); + final int start = Integer.parseInt(splitLine[START_INDEX]); + final int end = Integer.parseInt(splitLine[END_INDEX]); + return new SequenceRegion(contig, start, end); + } + + @Override + String encode(final Object object) { + if (object == null) { + throw new TribbleException("Cannot encode null in SEQUENCE_REGION_DIRECTIVE"); + } + if (!(object instanceof SequenceRegion)) { + throw new TribbleException("Cannot encode object of type " + object.getClass() + " in SEQUENCE_REGION_DIRECTIVE"); + } + + final SequenceRegion sequenceRegion = (SequenceRegion) object; + return "##sequence-region " + Gff3Writer.encodeString(sequenceRegion.getContig()) + " " + sequenceRegion.getStart() + " " + sequenceRegion.getEnd(); + } + }, + + FLUSH_DIRECTIVE("###$") { + @Override + String encode(final Object object) { + return "###"; + } + }, + + FASTA_DIRECTIVE("##FASTA$") { + @Override + String encode(final Object object) { + return "##FASTA"; + } + }; + + protected final Pattern regexPattern; + + Gff3Directive(String regex) { + this.regexPattern = Pattern.compile(regex); + } + + public static Gff3Directive toDirective(final String line) { + for (final Gff3Directive directive : Gff3Directive.values()) { + if(directive.regexPattern.matcher(line).matches()) { + return directive; + } + } + return null; + } + + protected Object decode(final String line) throws IOException { + return null; + } + + abstract String encode(final Object object); + } + +} From 7f907a439c6bc39e3bfcd64270476a07244a0345 Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Sun, 13 Apr 2025 20:52:04 +0200 Subject: [PATCH 2/7] cont --- .gitignore | 1 + .../htsjdk/samtools/util/FileExtensions.java | 1 + .../htsjdk/tribble/gff/AbstractGxxCodec.java | 73 +- .../tribble/gff/AbstractGxxConstants.java | 10 + .../htsjdk/tribble/gff/AbstractGxxWriter.java | 132 +++ .../java/htsjdk/tribble/gff/Gff3Codec.java | 64 +- .../htsjdk/tribble/gff/Gff3Constants.java | 8 +- .../java/htsjdk/tribble/gff/Gff3Writer.java | 122 +-- .../java/htsjdk/tribble/gff/GtfCodec.java | 758 ++++++------------ .../java/htsjdk/tribble/gff/GtfConstants.java | 7 + .../java/htsjdk/tribble/gff/GtfWriter.java | 52 ++ .../java/htsjdk/tribble/gff/GtfCodecTest.java | 72 ++ 12 files changed, 606 insertions(+), 694 deletions(-) create mode 100644 src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java create mode 100644 src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java create mode 100644 src/main/java/htsjdk/tribble/gff/GtfConstants.java create mode 100644 src/main/java/htsjdk/tribble/gff/GtfWriter.java create mode 100644 src/test/java/htsjdk/tribble/gff/GtfCodecTest.java diff --git a/.gitignore b/.gitignore index 03a8d6d509..5a200e58dd 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ src/htsjdk.iml #japi-compliance-checker /compat_reports/ +/bin/ diff --git a/src/main/java/htsjdk/samtools/util/FileExtensions.java b/src/main/java/htsjdk/samtools/util/FileExtensions.java index 63e2cf4de6..91f0419b5c 100755 --- a/src/main/java/htsjdk/samtools/util/FileExtensions.java +++ b/src/main/java/htsjdk/samtools/util/FileExtensions.java @@ -81,4 +81,5 @@ public final class FileExtensions { public static final Set BLOCK_COMPRESSED = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(".gz", ".gzip", ".bgz", ".bgzf"))); public static final Set GFF3 = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(".gff3", ".gff", ".gff3.gz", ".gff.gz"))); + public static final Set GTF = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(".gtf", ".gtf.gz"))); } diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java index 2c44aa47ae..2aef3f19bf 100644 --- a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java @@ -1,31 +1,35 @@ package htsjdk.tribble.gff; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.function.Predicate; + import htsjdk.samtools.util.CloserUtil; -import htsjdk.samtools.util.FileExtensions; -import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.LocationAware; - -import htsjdk.samtools.util.Log; import htsjdk.tribble.AbstractFeatureCodec; import htsjdk.tribble.Feature; import htsjdk.tribble.FeatureCodecHeader; import htsjdk.tribble.TribbleException; import htsjdk.tribble.annotation.Strand; import htsjdk.tribble.index.tabix.TabixFormat; -import htsjdk.tribble.readers.*; +import htsjdk.tribble.readers.AsciiLineReader; +import htsjdk.tribble.readers.AsciiLineReaderIterator; +import htsjdk.tribble.readers.LineIterator; +import htsjdk.tribble.readers.LineIteratorImpl; +import htsjdk.tribble.readers.SynchronousLineReader; import htsjdk.tribble.util.ParsingUtils; - - -import java.io.*; -import java.net.URLDecoder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; - /** * Abstract Base Codec for parsing Gff3 files or GTF codec */ @@ -45,10 +49,7 @@ public abstract class AbstractGxxCodec extends AbstractFeatureCodec activeFeatures = new ArrayDeque<>(); protected final Queue featuresToFlush = new ArrayDeque<>(); - protected final Map> activeFeaturesWithIDs = new HashMap<>(); - protected final Map> activeParentIDs = new HashMap<>(); protected final Map commentsWithLineNumbers = new LinkedHashMap<>(); @@ -96,7 +97,7 @@ public final Gff3Feature decode(final LineIterator lineIterator) throws IOExcept protected final Gff3BaseData parseLine(final String line, final int currentLine, final Predicate filterOutAttribute) { - final List splitLine = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); + final List splitLine = ParsingUtils.split(line, AbstractGxxConstants.FIELD_DELIMITER); if (splitLine.size() != NUM_FIELDS) { throw new TribbleException("Found an invalid number of columns in the given Gff3/GTF file at line + " + currentLine + " - Given: " + splitLine.size() + " Expected: " + NUM_FIELDS + " : " + line); @@ -108,8 +109,8 @@ protected final Gff3BaseData parseLine(final String line, final int currentLine, final String type = URLDecoder.decode(splitLine.get(FEATURE_TYPE_INDEX), "UTF-8"); final int start = Integer.parseInt(splitLine.get(START_LOCATION_INDEX)); final int end = Integer.parseInt(splitLine.get(END_LOCATION_INDEX)); - final double score = splitLine.get(SCORE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Double.parseDouble(splitLine.get(SCORE_INDEX)); - final int phase = splitLine.get(GENOMIC_PHASE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); + final double score = splitLine.get(SCORE_INDEX).equals(AbstractGxxConstants.UNDEFINED_FIELD_VALUE) ? -1 : Double.parseDouble(splitLine.get(SCORE_INDEX)); + final int phase = splitLine.get(GENOMIC_PHASE_INDEX).equals(AbstractGxxConstants.UNDEFINED_FIELD_VALUE) ? -1 : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); final Strand strand = Strand.decode(splitLine.get(GENOMIC_STRAND_INDEX)); final Map> attributes = parseAttributesColumn(splitLine.get(EXTRA_FIELDS_INDEX)); /* remove attibutes matching 'filterOutAttribute' */ @@ -146,22 +147,28 @@ public final Feature decodeLoc(LineIterator lineIterator) throws IOException { @Override public abstract boolean canDecode(final String inputFilePath); - static List decodeAttributeValue(final String attributeValue) { - //split on VALUE_DELIMITER, then decode - final List splitValues = ParsingUtils.split(attributeValue, Gff3Constants.VALUE_DELIMITER); + protected boolean canDecodeFirstLine(final String line) { + // make sure line conforms to gtf spec + final List fields = ParsingUtils.split(line, AbstractGxxConstants.FIELD_DELIMITER); + + if(fields.size() != NUM_FIELDS) return false;; - final List decodedValues = new ArrayList<>(); - for (final String encodedValue : splitValues) { + // check that start and end fields are integers try { - decodedValues.add(URLDecoder.decode(encodedValue.trim(), "UTF-8")); - } catch (final UnsupportedEncodingException ex) { - throw new TribbleException("Error decoding attribute " + encodedValue, ex); + /* final int start = */ Integer.parseInt(fields.get(3)); + /* final int end = */ Integer.parseInt(fields.get(4)); + } catch (NumberFormatException | NullPointerException nfe) { + return false; } - } - return decodedValues; + // check for strand + final String strand = fields.get(GENOMIC_STRAND_INDEX); + return strand.equals(Strand.POSITIVE.toString()) || + strand.equals(Strand.NEGATIVE.toString()) || + strand.equals(Strand.NONE.toString()) || + strand.equals("?"); } - + static String extractSingleAttribute(final List values) { if (values == null || values.isEmpty()) { return null; diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java new file mode 100644 index 0000000000..15032a7b8e --- /dev/null +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java @@ -0,0 +1,10 @@ +package htsjdk.tribble.gff; + +public class AbstractGxxConstants { + public static final char ATTRIBUTE_DELIMITER = ';'; + public static final char FIELD_DELIMITER = '\t'; + public static final String COMMENT_START = "#"; + public static final String DIRECTIVE_START = "##"; + public static final String UNDEFINED_FIELD_VALUE = "."; + public final static char END_OF_LINE_CHARACTER = '\n'; +} diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java new file mode 100644 index 0000000000..dbbf5c84d0 --- /dev/null +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java @@ -0,0 +1,132 @@ +package htsjdk.tribble.gff; + +import htsjdk.samtools.util.BlockCompressedOutputStream; +import htsjdk.samtools.util.IOUtil; +import htsjdk.tribble.TribbleException; + +import java.io.BufferedOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + + +/** + * A class to write out gff3/gtf files. Features are added using {@link #addFeature(Gff3Feature)}, directives using {@link #addDirective(Gff3Codec.Gff3Directive)}, + * and comments using {@link #addComment(String)}. Note that the version 3 directive is automatically added at creation, so should not be added separately. + */ +public abstract class AbstractGxxWriter implements Closeable { + + protected final OutputStream out; + + protected AbstractGxxWriter(final Path path, final Set fileExtensions) throws IOException { + if (fileExtensions.stream().noneMatch(e -> path.toString().endsWith(e))) { + throw new TribbleException("File " + path + " does not have extension consistent with gff3/gtf"); + } + + final OutputStream outputStream = IOUtil.hasGzipFileExtension(path)? new BlockCompressedOutputStream(path.toFile()) : Files.newOutputStream(path); + out = new BufferedOutputStream(outputStream); + } + + protected AbstractGxxWriter(final OutputStream stream) { + out = stream; + } + + protected final void writeWithNewLine(final String txt) throws IOException { + out.write(txt.getBytes()); + out.write(AbstractGxxConstants.END_OF_LINE_CHARACTER); + } + + protected final void tryToWrite(final String string) { + try { + out.write(string.getBytes()); + } catch (final IOException ex) { + throw new TribbleException("Error writing out string " + string, ex); + } + } + + protected final void writeFirstEightFields(final Gff3Feature feature) throws IOException { + writeJoinedByDelimiter(AbstractGxxConstants.FIELD_DELIMITER, this::tryToWrite, Arrays.asList( + escapeString(feature.getContig()), + escapeString(feature.getSource()), + escapeString(feature.getType()), + Integer.toString(feature.getStart()), + Integer.toString(feature.getEnd()), + feature.getScore() < 0 ? AbstractGxxConstants.UNDEFINED_FIELD_VALUE : Double.toString(feature.getScore()), + feature.getStrand().toString(), + feature.getPhase() < 0 ? AbstractGxxConstants.UNDEFINED_FIELD_VALUE : Integer.toString(feature.getPhase()) + ) + ); + } + + protected abstract void writeAttributes(final Map> attributes) throws IOException; + + protected final void writeJoinedByDelimiter(final char delimiter, final Consumer consumer, final Collection fields) throws IOException { + boolean isNotFirstField = false; + for (final T field : fields) { + if (isNotFirstField) { + out.write(delimiter); + } else { + isNotFirstField = true; + } + + consumer.accept(field); + } + } + + /*** + * add a feature + * @param feature the feature to be added + * @throws IOException + */ + public final void addFeature(final Gff3Feature feature) throws IOException { + writeFirstEightFields(feature); + out.write(AbstractGxxConstants.FIELD_DELIMITER); + writeAttributes(feature.getAttributes()); + out.write(AbstractGxxConstants.END_OF_LINE_CHARACTER); + } + + /*** + * escape a String. + * Default behavior is to call {@link #encodeString(String)} + * @param s the string to be escaped + * @return the escaped string + */ + protected String escapeString(final String s) { + return encodeString(s); + } + + static String encodeString(final String s) { + try { + //URLEncoder.encode is hardcoded to change all spaces to +, but we want spaces left unchanged so have to do this + //+ is escaped to %2B, so no loss of information + return URLEncoder.encode(s, "UTF-8").replace("+", " "); + } catch (final UnsupportedEncodingException ex) { + throw new TribbleException("Encoding failure", ex); + } + } + + /** + * Add comment line + * @param comment the comment line (not including leading #) + * @throws IOException + */ + public final void addComment(final String comment) throws IOException { + out.write(AbstractGxxConstants.COMMENT_START.getBytes()); + writeWithNewLine(comment); + } + + @Override + public final void close() throws IOException { + out.close(); + } +} \ No newline at end of file diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java index 0e9250e339..d66128cb96 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java @@ -9,12 +9,15 @@ import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -44,6 +47,9 @@ public class Gff3Codec extends AbstractGxxCodec { private static final String ARTEMIS_FASTA_MARKER = ">"; + protected final Queue activeFeatures = new ArrayDeque<>(); + private final Map> activeFeaturesWithIDs = new HashMap<>(); + private final Map> activeParentIDs = new HashMap<>(); private final Map sequenceRegionMap = new LinkedHashMap<>(); private final static Log logger = Log.getInstance(Gff3Codec.class); @@ -218,56 +224,32 @@ private void validateFeature(final Gff3Feature feature) { @Override public boolean canDecode(final String inputFilePath) { - boolean canDecode; try { // Simple file and name checks to start with: Path p = IOUtil.getPath(inputFilePath); - canDecode = FileExtensions.GFF3.stream().anyMatch(fe -> p.toString().endsWith(fe)); + if(!FileExtensions.GFF3.stream().anyMatch(fe -> p.toString().endsWith(fe))) { + return false; + } - if (canDecode) { + // Crack open the file and look at the top of it: + final InputStream inputStream = IOUtil.hasGzipFileExtension(p)? new GZIPInputStream(Files.newInputStream(p)) : Files.newInputStream(p); - // Crack open the file and look at the top of it: - final InputStream inputStream = IOUtil.hasGzipFileExtension(p)? new GZIPInputStream(Files.newInputStream(p)) : Files.newInputStream(p); + try ( BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) ) { - try ( BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) ) { + String line = br.readLine(); - String line = br.readLine(); - - // First line must be GFF version directive - if (Gff3Directive.toDirective(line) != Gff3Directive.VERSION3_DIRECTIVE) { + // First line must be GFF version directive + if (Gff3Directive.toDirective(line) != Gff3Directive.VERSION3_DIRECTIVE) { + return false; + } + while (line.startsWith(Gff3Constants.COMMENT_START)) { + line = br.readLine(); + if ( line == null ) { return false; } - while (line.startsWith(Gff3Constants.COMMENT_START)) { - line = br.readLine(); - if ( line == null ) { - return false; - } - } - - // make sure line conforms to gtf spec - final List fields = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); - - canDecode &= fields.size() == NUM_FIELDS; - - if (canDecode) { - // check that start and end fields are integers - try { - /* final int start = */ Integer.parseInt(fields.get(3)); - /* final int end = */ Integer.parseInt(fields.get(4)); - } catch (NumberFormatException | NullPointerException nfe) { - return false; - } - - // check for strand - - final String strand = fields.get(GENOMIC_STRAND_INDEX); - canDecode &= strand.equals(Strand.POSITIVE.toString()) || - strand.equals(Strand.NEGATIVE.toString()) || - strand.equals(Strand.NONE.toString()) || - strand.equals("?"); - } } + return canDecodeFirstLine(line); } } catch (final FileNotFoundException ex) { @@ -277,8 +259,6 @@ public boolean canDecode(final String inputFilePath) { catch (final IOException ex) { return false; } - - return canDecode; } static List decodeAttributeValue(final String attributeValue) { @@ -366,7 +346,7 @@ private void prepareToFlushFeatures() { activeParentIDs.clear(); } - + @Override public boolean isDone(final LineIterator lineIterator) { return !lineIterator.hasNext() && activeFeatures.isEmpty() && featuresToFlush.isEmpty(); diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Constants.java b/src/main/java/htsjdk/tribble/gff/Gff3Constants.java index fa49b78be8..5e9f231959 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Constants.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Constants.java @@ -1,15 +1,9 @@ package htsjdk.tribble.gff; -public class Gff3Constants { - public static final char FIELD_DELIMITER = '\t'; - public static final char ATTRIBUTE_DELIMITER = ';'; +public class Gff3Constants extends AbstractGxxConstants { public static final char KEY_VALUE_SEPARATOR = '='; public static final char VALUE_DELIMITER = ','; - public static final String COMMENT_START = "#"; - public static final String DIRECTIVE_START = "##"; - public static final String UNDEFINED_FIELD_VALUE = "."; public static final String PARENT_ATTRIBUTE_KEY = "Parent"; - public final static char END_OF_LINE_CHARACTER = '\n'; public static final String ID_ATTRIBUTE_KEY = "ID"; public static final String NAME_ATTRIBUTE_KEY = "Name"; public static final String ALIAS_ATTRIBUTE_KEY = "Alias"; diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Writer.java b/src/main/java/htsjdk/tribble/gff/Gff3Writer.java index 0cee78ce73..120fb9809e 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Writer.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Writer.java @@ -1,47 +1,30 @@ package htsjdk.tribble.gff; -import htsjdk.samtools.util.BlockCompressedOutputStream; -import htsjdk.samtools.util.FileExtensions; -import htsjdk.samtools.util.IOUtil; -import htsjdk.tribble.TribbleException; - -import java.io.BufferedOutputStream; -import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.function.Consumer; + +import htsjdk.samtools.util.FileExtensions; +import htsjdk.tribble.TribbleException; /** * A class to write out gff3 files. Features are added using {@link #addFeature(Gff3Feature)}, directives using {@link #addDirective(Gff3Codec.Gff3Directive)}, * and comments using {@link #addComment(String)}. Note that the version 3 directive is automatically added at creation, so should not be added separately. */ -public class Gff3Writer implements Closeable { +public class Gff3Writer extends AbstractGxxWriter { - private final OutputStream out; private final static String version = "3.1.25"; public Gff3Writer(final Path path) throws IOException { - if (FileExtensions.GFF3.stream().noneMatch(e -> path.toString().endsWith(e))) { - throw new TribbleException("File " + path + " does not have extension consistent with gff3"); - } - - final OutputStream outputStream = IOUtil.hasGzipFileExtension(path)? new BlockCompressedOutputStream(path.toFile()) : Files.newOutputStream(path); - out = new BufferedOutputStream(outputStream); - //start with version directive + super(path, FileExtensions.GFF3); initialize(); - } + } public Gff3Writer(final OutputStream stream) { - out = stream; + super(stream); initialize(); } @@ -53,34 +36,9 @@ private void initialize() { } } - private void writeWithNewLine(final String txt) throws IOException { - out.write(txt.getBytes()); - out.write(Gff3Constants.END_OF_LINE_CHARACTER); - } - - private void tryToWrite(final String string) { - try { - out.write(string.getBytes()); - } catch (final IOException ex) { - throw new TribbleException("Error writing out string " + string, ex); - } - } - - private void writeFirstEightFields(final Gff3Feature feature) throws IOException { - writeJoinedByDelimiter(Gff3Constants.FIELD_DELIMITER, this::tryToWrite, Arrays.asList( - escapeString(feature.getContig()), - escapeString(feature.getSource()), - escapeString(feature.getType()), - Integer.toString(feature.getStart()), - Integer.toString(feature.getEnd()), - feature.getScore() < 0 ? Gff3Constants.UNDEFINED_FIELD_VALUE : Double.toString(feature.getScore()), - feature.getStrand().toString(), - feature.getPhase() < 0 ? Gff3Constants.UNDEFINED_FIELD_VALUE : Integer.toString(feature.getPhase()) - ) - ); - } - void writeAttributes(final Map> attributes) throws IOException { + @Override + protected void writeAttributes(final Map> attributes) throws IOException { if (attributes.isEmpty()) { out.write(Gff3Constants.UNDEFINED_FIELD_VALUE.getBytes()); } @@ -97,52 +55,7 @@ void writeKeyValuePair(final String key, final List values) { throw new TribbleException("error writing out key value pair " + key + " " + values); } } - - private void writeJoinedByDelimiter(final char delimiter, final Consumer consumer, final Collection fields) throws IOException { - boolean isNotFirstField = false; - for (final T field : fields) { - if (isNotFirstField) { - out.write(delimiter); - } else { - isNotFirstField = true; - } - - consumer.accept(field); - } - } - - /*** - * add a feature - * @param feature the feature to be added - * @throws IOException - */ - public void addFeature(final Gff3Feature feature) throws IOException { - writeFirstEightFields(feature); - out.write(Gff3Constants.FIELD_DELIMITER); - writeAttributes(feature.getAttributes()); - out.write(Gff3Constants.END_OF_LINE_CHARACTER); - } - - /*** - * escape a String. - * Default behavior is to call {@link #encodeString(String)} - * @param s the string to be escaped - * @return the escaped string - */ - protected String escapeString(final String s) { - return encodeString(s); - } - - static String encodeString(final String s) { - try { - //URLEncoder.encode is hardcoded to change all spaces to +, but we want spaces left unchanged so have to do this - //+ is escaped to %2B, so no loss of information - return URLEncoder.encode(s, "UTF-8").replace("+", " "); - } catch (final UnsupportedEncodingException ex) { - throw new TribbleException("Encoding failure", ex); - } - } - + /** * Add a directive with an object * @param directive the directive to be added @@ -167,19 +80,4 @@ public void addDirective(final Gff3Codec.Gff3Directive directive) throws IOExcep } addDirective(directive, null); } - - /** - * Add comment line - * @param comment the comment line (not including leading #) - * @throws IOException - */ - public void addComment(final String comment) throws IOException { - out.write(Gff3Constants.COMMENT_START.getBytes()); - writeWithNewLine(comment); - } - - @Override - public void close() throws IOException { - out.close(); - } } \ No newline at end of file diff --git a/src/main/java/htsjdk/tribble/gff/GtfCodec.java b/src/main/java/htsjdk/tribble/gff/GtfCodec.java index 38dea06cb8..de6e260571 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfCodec.java +++ b/src/main/java/htsjdk/tribble/gff/GtfCodec.java @@ -1,114 +1,75 @@ package htsjdk.tribble.gff; -import htsjdk.samtools.util.CloserUtil; -import htsjdk.samtools.util.FileExtensions; -import htsjdk.samtools.util.IOUtil; -import htsjdk.samtools.util.LocationAware; - -import htsjdk.samtools.util.Log; -import htsjdk.tribble.AbstractFeatureCodec; -import htsjdk.tribble.Feature; -import htsjdk.tribble.FeatureCodecHeader; -import htsjdk.tribble.TribbleException; -import htsjdk.tribble.annotation.Strand; -import htsjdk.tribble.index.tabix.TabixFormat; -import htsjdk.tribble.readers.*; -import htsjdk.tribble.util.ParsingUtils; - - - -import java.io.*; -import java.net.URLDecoder; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.function.Predicate; -import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; +import htsjdk.samtools.util.FileExtensions; +import htsjdk.samtools.util.IOUtil; +import htsjdk.samtools.util.Log; +import htsjdk.tribble.TribbleException; +import htsjdk.tribble.gff.AbstractGxxCodec.DecodeDepth; +import htsjdk.tribble.readers.LineIterator; + /** - * Codec for parsing Gff3 files, as defined in https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md - * Note that while spec states that all feature types must be defined in sequence ontology, this implementation makes no check on feature types, and allows any string as feature type - * - * Each feature line in the Gff3 file will be emitted as a separate feature. Features linked together through the "Parent" attribute will be linked through {@link Gff3Feature#getParents()}, {@link Gff3Feature#getChildren()}, - * {@link Gff3Feature#getAncestors()}, {@link Gff3Feature#getDescendents()}, amd {@link Gff3Feature#flatten()}. This linking is not guaranteed to be comprehensive when the file is read for only features overlapping a particular - * region, using a tribble index. In this case, a particular feature will only be linked to the subgroup of features it is linked to in the input file which overlap the given region. + * Codec for parsing GTF files */ -public class GtfCodec extends AbstractFeatureCodec { - - - private static final int NUM_FIELDS = 9; - - private static final int CHROMOSOME_NAME_INDEX = 0; - private static final int ANNOTATION_SOURCE_INDEX = 1; - private static final int FEATURE_TYPE_INDEX = 2; - private static final int START_LOCATION_INDEX = 3; - private static final int END_LOCATION_INDEX = 4; - private static final int SCORE_INDEX = 5; - private static final int GENOMIC_STRAND_INDEX = 6; - private static final int GENOMIC_PHASE_INDEX = 7; - private static final int EXTRA_FIELDS_INDEX = 8; - - - private static final String IS_CIRCULAR_ATTRIBUTE_KEY = "Is_circular"; - - private static final String ARTEMIS_FASTA_MARKER = ">"; - - private final Queue activeFeatures = new ArrayDeque<>(); - private final Queue featuresToFlush = new ArrayDeque<>(); - private final Map> activeFeaturesWithIDs = new HashMap<>(); - private final Map> activeParentIDs = new HashMap<>(); - - private final Map sequenceRegionMap = new LinkedHashMap<>(); - private final Map commentsWithLineNumbers = new LinkedHashMap<>(); - - private final static Log logger = Log.getInstance(Gff3Codec.class); - - private boolean reachedFasta = false; - - private DecodeDepth decodeDepth; +public class GtfCodec extends AbstractGxxCodec { + + private final static Log logger = Log.getInstance(GtfCodec.class); + private final Map activeGenes = new HashMap<>(); + private final Map activeTranscripts = new HashMap<>(); + private final Map> activeTranscriptComponents = new HashMap<>(); + private final List activeUncharacterized = new ArrayList<>(); - private int currentLine = 0; - - /** filter to removing keys from the EXTRA_FIELDS column */ - private final Predicate filterOutAttribute; - public Gff3Codec() { + public GtfCodec() { this(DecodeDepth.DEEP); } - public Gff3Codec(final DecodeDepth decodeDepth) { + public GtfCodec(final DecodeDepth decodeDepth) { this(decodeDepth, KEY -> false); } - /** * @param decodeDepth a value from DecodeDepth * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column */ - public Gff3Codec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { - super(Gff3Feature.class); - this.decodeDepth = decodeDepth; - this.filterOutAttribute = filterOutAttribute; + public GtfCodec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { + super(decodeDepth,filterOutAttribute); /* check required keys are always kept */ - for (final String key : new String[] {Gff3Constants.PARENT_ATTRIBUTE_KEY, Gff3Constants.ID_ATTRIBUTE_KEY, Gff3Constants.NAME_ATTRIBUTE_KEY}) { + for (final String key : new String[] {GtfConstants.GENE_ID, GtfConstants.TRANSCRIPT_ID}) { if (filterOutAttribute.test(key)) { throw new IllegalArgumentException("Predicate should always accept " + key); } } - } + } - public enum DecodeDepth { - DEEP , - SHALLOW + private boolean isGene(final Gff3Feature feature) { + return feature.getType().equals("gene"); } - @Override - public Gff3Feature decode(final LineIterator lineIterator) throws IOException { - return decode(lineIterator, decodeDepth); + private boolean isTranscript(final Gff3Feature feature) { + return feature.getType().equals("transcript") || feature.getType().equalsIgnoreCase("mrna") ; } - - private Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) throws IOException { + + + + @Override + protected Gff3Feature decode(LineIterator lineIterator, DecodeDepth depth) throws IOException { currentLine++; /* Basic strategy: Load features into deque, create maps from a features ID to it, and from a features parents' IDs to it. For each feature, link to parents using these maps. @@ -123,232 +84,211 @@ private Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth de final String line = lineIterator.next(); - if (reachedFasta) { - //previously reached fasta, flush whatever is active - prepareToFlushFeatures(); - return featuresToFlush.poll(); - } - if (line.startsWith(ARTEMIS_FASTA_MARKER)) { - //backwards compatability with Artemis is built into gff3 spec - processDirective(Gff3Directive.FASTA_DIRECTIVE, null); + if (line.startsWith(GtfConstants.COMMENT_START)) { + commentsWithLineNumbers.put(currentLine, line.substring(GtfConstants.COMMENT_START.length())); return featuresToFlush.poll(); } - if (line.startsWith(Gff3Constants.COMMENT_START) && !line.startsWith(Gff3Constants.DIRECTIVE_START)) { - commentsWithLineNumbers.put(currentLine, line.substring(Gff3Constants.COMMENT_START.length())); - return featuresToFlush.poll(); - } - - if (line.startsWith(Gff3Constants.DIRECTIVE_START)) { - parseDirective(line); - return featuresToFlush.poll(); - } - - - - final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, this.filterOutAttribute)); - activeFeatures.add(thisFeature); - if (depth == DecodeDepth.DEEP) { - //link to parents/children/co-features - final List parentIDs = thisFeature.getAttribute(Gff3Constants.PARENT_ATTRIBUTE_KEY); - final String id = thisFeature.getID(); - - for (final String parentID : parentIDs) { - - final Set theseParents = activeFeaturesWithIDs.get(parentID); - if (theseParents != null) { - for (final Gff3FeatureImpl parent : theseParents) { - thisFeature.addParent(parent); - } - } - if (activeParentIDs.containsKey(parentID)) { - activeParentIDs.get(parentID).add(thisFeature); - } else { - activeParentIDs.put(parentID, new HashSet<>(Collections.singleton(thisFeature))); - } - } - - if (id != null) { - if (activeFeaturesWithIDs.containsKey(id)) { - for (final Gff3FeatureImpl coFeature : activeFeaturesWithIDs.get(id)) { - thisFeature.addCoFeature(coFeature); - } - activeFeaturesWithIDs.get(id).add(thisFeature); - } else { - activeFeaturesWithIDs.put(id, new HashSet<>(Collections.singleton(thisFeature))); - } - } - - if (activeParentIDs.containsKey(thisFeature.getID())) { - for (final Gff3FeatureImpl child : activeParentIDs.get(thisFeature.getID())) { - child.addParent(thisFeature); - } - } - } - - validateFeature(thisFeature); - if (depth == DecodeDepth.SHALLOW) { - //flush all features immediatly - prepareToFlushFeatures(); + + final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, super.filterOutAttribute)); + switch(depth) { + case SHALLOW: + this.activeUncharacterized.add(thisFeature); + //flush all features immediatly + prepareToFlushFeatures(); + break; + case DEEP: + if(isGene(thisFeature)) { + final String gene_id = thisFeature.getUniqueAttribute(GtfConstants.GENE_ID).orElseThrow(()-> + new TribbleException("attribute "+GtfConstants.GENE_ID+" missing in "+line) + ); + if(activeGenes.containsKey(gene_id)) { + throw new TribbleException("duplicate gene "+ gene_id +" in "+line); + } + activeGenes.put(gene_id, thisFeature); + } else if(isTranscript(thisFeature)) { + final String transcript_id = thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> + new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+line) + ); + if(activeTranscripts.containsKey(transcript_id)) { + throw new TribbleException("duplicate transcript "+ transcript_id +" in "+line); + } + activeTranscripts.put(transcript_id, thisFeature); + } else if(thisFeature.hasAttribute(GtfConstants.TRANSCRIPT_ID)){ + final String transcript_id = thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> + new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+line) + ); + List components =this.activeTranscriptComponents.get(transcript_id); + if(components==null) { + components = new ArrayList<>(); + this.activeTranscriptComponents.put(transcript_id, components); + } + components.add(thisFeature); + } else { + this.activeUncharacterized.add(thisFeature); + } + break; } + return featuresToFlush.poll(); } - - /** - * Parse attributes field for gff3 feature - * @param attributesString attributes field string from line in gff3 file - * @return map of keys to values for attributes of this feature - * @throws UnsupportedEncodingException - */ - static private Map> parseAttributes(final String attributesString) throws UnsupportedEncodingException { - if (attributesString.equals(Gff3Constants.UNDEFINED_FIELD_VALUE)) { + @Override + protected Map> parseAttributesColumn(final String attributesString) throws UnsupportedEncodingException { + if (attributesString.equals(GtfConstants.UNDEFINED_FIELD_VALUE)) { return Collections.emptyMap(); - } + } final Map> attributes = new LinkedHashMap<>(); - final List splitLine = ParsingUtils.split(attributesString,Gff3Constants.ATTRIBUTE_DELIMITER); - for(String attribute : splitLine) { - final List key_value = ParsingUtils.split(attribute,Gff3Constants.KEY_VALUE_SEPARATOR); - if (key_value.size() != 2) { - throw new TribbleException("Attribute string " + attributesString + " is invalid"); + + final int len = attributesString.length(); + int i = 0; + for (;;) { + // skip whitespaces + while (i < len && Character.isWhitespace(attributesString.charAt(i))) { + i++; } - attributes.put(URLDecoder.decode(key_value.get(0).trim(), "UTF-8"), decodeAttributeValue(key_value.get(1).trim())); - } - return attributes; - } - - private static Gff3BaseData parseLine(final String line, final int currentLine, final Predicate filterOutAttribute) { - final List splitLine = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); - - if (splitLine.size() != NUM_FIELDS) { - throw new TribbleException("Found an invalid number of columns in the given Gff3 file at line + " + currentLine + " - Given: " + splitLine.size() + " Expected: " + NUM_FIELDS + " : " + line); - } + // end of string + if (i >= len) { + break; + } + + final StringBuilder keyBuilder = new StringBuilder(); - try { - final String contig = URLDecoder.decode(splitLine.get(CHROMOSOME_NAME_INDEX), "UTF-8"); - final String source = URLDecoder.decode(splitLine.get(ANNOTATION_SOURCE_INDEX), "UTF-8"); - final String type = URLDecoder.decode(splitLine.get(FEATURE_TYPE_INDEX), "UTF-8"); - final int start = Integer.parseInt(splitLine.get(START_LOCATION_INDEX)); - final int end = Integer.parseInt(splitLine.get(END_LOCATION_INDEX)); - final double score = splitLine.get(SCORE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Double.parseDouble(splitLine.get(SCORE_INDEX)); - final int phase = splitLine.get(GENOMIC_PHASE_INDEX).equals(Gff3Constants.UNDEFINED_FIELD_VALUE) ? -1 : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); - final Strand strand = Strand.decode(splitLine.get(GENOMIC_STRAND_INDEX)); - final Map> attributes = parseAttributes(splitLine.get(EXTRA_FIELDS_INDEX)); - /* remove attibutes matching 'filterOutAttribute' */ - attributes.keySet().removeIf(filterOutAttribute); - return new Gff3BaseData(contig, source, type, start, end, score, strand, phase, attributes); - } catch (final NumberFormatException ex ) { - throw new TribbleException("Cannot read integer value for start/end position from line " + currentLine + ". Line is: " + line, ex); - } catch (final IOException ex) { - throw new TribbleException("Cannot decode feature info from line " + currentLine + ". Line is: " + line, ex); - } - } + // consumme key + while (i < len && !Character.isWhitespace(attributesString.charAt(i))) { + keyBuilder.append(attributesString.charAt(i)); + i++; + } + // skip whitespaces + while (i < len && Character.isWhitespace(attributesString.charAt(i))) { + i++; + } - /** - * Get list of sequence regions parsed by the codec. - * @return list of sequence regions - */ - public List getSequenceRegions() { - return Collections.unmodifiableList(new ArrayList<>(sequenceRegionMap.values())); - } + final String key = keyBuilder.toString(); - /** - * Gets map from line number to comment found on that line. The text of the comment EXCLUDES the leading # which indicates a comment line. - * @return Map from line number to comment found on line - */ - public Map getCommentsWithLineNumbers() { - return Collections.unmodifiableMap(new LinkedHashMap<>(commentsWithLineNumbers)); - } - /** - * Gets list of comments parsed by the codec. Excludes leading # which indicates a comment line. - * @return - */ - public List getCommentTexts() { - return Collections.unmodifiableList(new ArrayList<>(commentsWithLineNumbers.values())); - } + // no value + if (i >= len) { + logger.warn("no value for '" + key + "' in " + attributesString); + attributes.put(key,Collections.singletonList("")); + break; + } + + final List values=new ArrayList<>(1); + // read multiple values + for(;;) { + /* read VALUE */ + final StringBuilder valueBuilder = new StringBuilder(); + // first char of value + char c = attributesString.charAt(i); + + if (c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { // no value + i++; + values.add(""); + logger.warn("no value for '" + key + "' in " + attributesString); + break; + } + + + // quoted string + if (c == '\"') { + i++; + while (i < len) { + c = attributesString.charAt(i); + ++i; + if (c == '\\') { + c = (i < len ? attributesString.charAt(i) : '\0'); + ++i; + switch (c) { + case '"': + valueBuilder.append("\""); + break; + case '\'': + valueBuilder.append("\'"); + break; + case 't': + valueBuilder.append("\t"); + break; + case 'n': + valueBuilder.append("\n"); + break; + default: + //logger.warn("unparsed value in " + attributesString); + break; + } + } else if (c == '\"') { + values.add(valueBuilder.toString()); + break; + } else { + valueBuilder.append(c); + } + } // end of while + + + + } + else /* not a quoted string */ + { + while (i < len) { + c = attributesString.charAt(i); + ++i; + if (Character.isWhitespace(c) || c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { + break; + } + valueBuilder.append(c); + } + values.add(valueBuilder.toString()); + } + + // skip whitespaces + while (i < len && Character.isWhitespace(attributesString.charAt(i))) { + i++; + } + if (i >= len) { + break; + } - /** - * If sequence region of feature's contig has been specified with sequence region directive, validates that - * feature's coordinates are within the specified sequence region. TribbleException is thrown if invalid. - * @param feature - */ - private void validateFeature(final Gff3Feature feature) { - if (sequenceRegionMap.containsKey(feature.getContig())) { - final SequenceRegion region = sequenceRegionMap.get(feature.getContig()); - if (feature.getStart() == region.getStart() && feature.getEnd() == region.getEnd()) { - //landmark feature - final boolean isCircular = Boolean.parseBoolean(extractSingleAttribute(feature.getAttribute(IS_CIRCULAR_ATTRIBUTE_KEY))); - region.setCircular(isCircular); - } - if (region.isCircular()? !region.overlaps(feature) : !region.contains(feature)) { - throw new TribbleException("feature at " + feature.getContig() + ":" + feature.getStart() + "-" + feature.getEnd() + - " not contained in specified sequence region (" + region.getContig() + ":" + region.getStart() + "-" + region.getEnd()); - } - } - } + if(attributesString.charAt(i)==AbstractGxxConstants.ATTRIBUTE_DELIMITER) { + i++; + break; + } + }//end of read multiple values + attributes.put(key, values); + } + return attributes; - @Override - public Feature decodeLoc(LineIterator lineIterator) throws IOException { - return decode(lineIterator, DecodeDepth.SHALLOW); } - @Override - public boolean canDecode(final String inputFilePath) { - boolean canDecode; + @Override + public boolean canDecode(final String inputFilePath) { try { // Simple file and name checks to start with: Path p = IOUtil.getPath(inputFilePath); - canDecode = FileExtensions.GFF3.stream().anyMatch(fe -> p.toString().endsWith(fe)); - - if (canDecode) { - - // Crack open the file and look at the top of it: - final InputStream inputStream = IOUtil.hasGzipFileExtension(p)? new GZIPInputStream(Files.newInputStream(p)) : Files.newInputStream(p); - - try ( BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) ) { - - String line = br.readLine(); - - // First line must be GFF version directive - if (Gff3Directive.toDirective(line) != Gff3Directive.VERSION3_DIRECTIVE) { - return false; - } - while (line.startsWith(Gff3Constants.COMMENT_START)) { - line = br.readLine(); - if ( line == null ) { - return false; - } - } - - // make sure line conforms to gtf spec - final List fields = ParsingUtils.split(line, Gff3Constants.FIELD_DELIMITER); - - canDecode &= fields.size() == NUM_FIELDS; - - if (canDecode) { - // check that start and end fields are integers - try { - final int start = Integer.parseInt(fields.get(3)); - final int end = Integer.parseInt(fields.get(4)); - } catch (NumberFormatException | NullPointerException nfe) { - return false; - } - - // check for strand - - final String strand = fields.get(GENOMIC_STRAND_INDEX); - canDecode &= strand.equals(Strand.POSITIVE.toString()) || - strand.equals(Strand.NEGATIVE.toString()) || - strand.equals(Strand.NONE.toString()) || - strand.equals("?"); - } + if(!FileExtensions.GTF.stream().anyMatch(fe -> p.toString().endsWith(fe))) { + return false; + } + + // Crack open the file and look at the top of it: + final InputStream inputStream = IOUtil.hasGzipFileExtension(p)? new GZIPInputStream(Files.newInputStream(p)) : Files.newInputStream(p); + + try ( BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) ) { + String line; + for(;;) { + line = br.readLine(); + if ( line == null ) { + return false; + } + if(line.startsWith(GtfConstants.COMMENT_START)) { + continue; + } + return canDecodeFirstLine(line); } - } + } + } - catch (FileNotFoundException ex) { + catch (final FileNotFoundException ex) { logger.error(inputFilePath + " not found."); return false; } @@ -356,231 +296,49 @@ public boolean canDecode(final String inputFilePath) { return false; } - return canDecode; - } - - static List decodeAttributeValue(final String attributeValue) { - //split on VALUE_DELIMITER, then decode - final List splitValues = ParsingUtils.split(attributeValue, Gff3Constants.VALUE_DELIMITER); - - final List decodedValues = new ArrayList<>(); - for (final String encodedValue : splitValues) { - try { - decodedValues.add(URLDecoder.decode(encodedValue.trim(), "UTF-8")); - } catch (final UnsupportedEncodingException ex) { - throw new TribbleException("Error decoding attribute " + encodedValue, ex); - } - } - - return decodedValues; - } - - static String extractSingleAttribute(final List values) { - if (values == null || values.isEmpty()) { - return null; - } - - if (values.size() != 1) { - throw new TribbleException("Attribute has multiple values when only one expected"); - } - return values.get(0); } - @Override - public FeatureCodecHeader readHeader(LineIterator lineIterator) { - - List header = new ArrayList<>(); - while(lineIterator.hasNext()) { - String line = lineIterator.peek(); - if (line.startsWith(Gff3Constants.COMMENT_START)) { - header.add(line); - lineIterator.next(); - } else { - break; - } - } - - return new FeatureCodecHeader(header, FeatureCodecHeader.NO_HEADER_END); - } - - /** - * Parse a directive line from a gff3 file - * @param directiveLine - * @throws IOException - */ - private void parseDirective(final String directiveLine) throws IOException { - final Gff3Directive directive = Gff3Directive.toDirective(directiveLine); - if (directive != null) { - processDirective(directive, directive.decode(directiveLine)); - - } else { - logger.warn("ignoring directive " + directiveLine); - } - } - - /** - * Process a gff3 directive - * @param directive the gff3 directive, indicated by a specific directive line - * @param decodedResult the decoding of the directive line by the given directive - */ - private void processDirective(final Gff3Directive directive, final Object decodedResult) { - switch (directive) { - case VERSION3_DIRECTIVE: - break; - - case SEQUENCE_REGION_DIRECTIVE: - final SequenceRegion newRegion = (SequenceRegion) decodedResult; - if (sequenceRegionMap.containsKey(newRegion.getContig())) { - throw new TribbleException("directive for sequence-region " + newRegion.getContig() + " included more than once."); - } - sequenceRegionMap.put(newRegion.getContig(), newRegion); - break; - - case FLUSH_DIRECTIVE: - prepareToFlushFeatures(); - break; - - case FASTA_DIRECTIVE: - reachedFasta = true; - break; - - default: - throw new IllegalArgumentException( "Directive " + directive + " has been added to Gff3Directive, but is not being handled by Gff3Codec::processDirective. This is a BUG."); - - } - } /** * move active top level features to featuresToFlush. clear active features. */ private void prepareToFlushFeatures() { - featuresToFlush.addAll(activeFeatures); - activeFeaturesWithIDs.clear(); - activeFeatures.clear(); - activeParentIDs.clear(); - } - - @Override - public LineIterator makeSourceFromStream(final InputStream bufferedInputStream) { - return new LineIteratorImpl(new SynchronousLineReader(bufferedInputStream)); - } - - @Override - public LocationAware makeIndexableSourceFromStream(final InputStream bufferedInputStream) { - return new AsciiLineReaderIterator(AsciiLineReader.from(bufferedInputStream)); - } - - @Override - public boolean isDone(final LineIterator lineIterator) { - return !lineIterator.hasNext() && activeFeatures.isEmpty() && featuresToFlush.isEmpty(); - } - - @Override - public void close(final LineIterator lineIterator) { - //cleanup resources - featuresToFlush.clear(); - activeFeaturesWithIDs.clear(); - activeFeatures.clear(); - activeParentIDs.clear(); - CloserUtil.close(lineIterator); - } - - @Override - public TabixFormat getTabixFormat() { - return TabixFormat.GFF; - } - - /** - * Enum for parsing directive lines. If information in directive line needs to be parsed beyond specifying directive type, decode method should be overriden - */ - public enum Gff3Directive { - - VERSION3_DIRECTIVE("##gff-version\\s+3(?:\\.\\d*)*$") { - @Override - protected Object decode(final String line) throws IOException { - final String[] splitLine = line.split("\\s+"); - return splitLine[1]; - } - - @Override - String encode(final Object object) { - if (object == null) { - throw new TribbleException("Cannot encode null in VERSION3_DIRECTIVE"); - } - if (!(object instanceof String)) { - throw new TribbleException("Cannot encode object of type " + object.getClass() + " in VERSION3_DIRECTIVE"); - } - - final String versionLine = "##gff-version " + (String)object; - if (!regexPattern.matcher(versionLine).matches()) { - throw new TribbleException("Version " + (String)object + " is not a valid version"); - } - - return versionLine; - } - }, - - SEQUENCE_REGION_DIRECTIVE("##sequence-region\\s+.+ \\d+ \\d+$") { - final private int CONTIG_INDEX = 1; - final private int START_INDEX = 2; - final private int END_INDEX = 3; - @Override - protected Object decode(final String line) throws IOException { - final String[] splitLine = line.split("\\s+"); - final String contig = URLDecoder.decode(splitLine[CONTIG_INDEX], "UTF-8"); - final int start = Integer.parseInt(splitLine[START_INDEX]); - final int end = Integer.parseInt(splitLine[END_INDEX]); - return new SequenceRegion(contig, start, end); - } - - @Override - String encode(final Object object) { - if (object == null) { - throw new TribbleException("Cannot encode null in SEQUENCE_REGION_DIRECTIVE"); - } - if (!(object instanceof SequenceRegion)) { - throw new TribbleException("Cannot encode object of type " + object.getClass() + " in SEQUENCE_REGION_DIRECTIVE"); - } - - final SequenceRegion sequenceRegion = (SequenceRegion) object; - return "##sequence-region " + Gff3Writer.encodeString(sequenceRegion.getContig()) + " " + sequenceRegion.getStart() + " " + sequenceRegion.getEnd(); - } - }, - - FLUSH_DIRECTIVE("###$") { - @Override - String encode(final Object object) { - return "###"; - } - }, - - FASTA_DIRECTIVE("##FASTA$") { - @Override - String encode(final Object object) { - return "##FASTA"; - } - }; - - protected final Pattern regexPattern; - - Gff3Directive(String regex) { - this.regexPattern = Pattern.compile(regex); - } - - public static Gff3Directive toDirective(final String line) { - for (final Gff3Directive directive : Gff3Directive.values()) { - if(directive.regexPattern.matcher(line).matches()) { - return directive; - } - } - return null; - } - - protected Object decode(final String line) throws IOException { - return null; - } - - abstract String encode(final Object object); + + activeTranscripts.values().stream().forEach(FEAT->{ + final String gene_id = FEAT.getUniqueAttribute(GtfConstants.GENE_ID).orElseThrow(()-> + new TribbleException("attribute "+GtfConstants.GENE_ID+" missing in "+FEAT) + ); + final Gff3FeatureImpl geneFeat = activeGenes.get(gene_id); + if(!activeGenes.containsKey(gene_id)) { + throw new TribbleException("undefined gene "+ gene_id +" in "+FEAT); + } + FEAT.addParent(geneFeat); + }); + + activeTranscriptComponents.values().stream().flatMap(L->L.stream()).forEach(FEAT->{ + final String transcript_id = FEAT.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> + new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+FEAT) + ); + final Gff3FeatureImpl transcriptFeat = activeTranscripts.get(transcript_id); + if(!activeTranscripts.containsKey(transcript_id)) { + throw new TribbleException("undefined transcript_id "+ transcript_id +" in "+FEAT); + } + FEAT.addParent(transcriptFeat); + }); + + + featuresToFlush.addAll(activeGenes.values()); + featuresToFlush.addAll(activeUncharacterized); + activeTranscripts.clear(); + activeTranscriptComponents.clear(); + activeGenes.clear(); + activeUncharacterized.clear(); } + + @Override + public boolean isDone(LineIterator lineIterator) { + // TODO Auto-generated method stub + return false; + } } diff --git a/src/main/java/htsjdk/tribble/gff/GtfConstants.java b/src/main/java/htsjdk/tribble/gff/GtfConstants.java new file mode 100644 index 0000000000..6cc94f46f3 --- /dev/null +++ b/src/main/java/htsjdk/tribble/gff/GtfConstants.java @@ -0,0 +1,7 @@ +package htsjdk.tribble.gff; + +public class GtfConstants extends AbstractGxxConstants { + public static final char VALUE_DELIMITER = ' '; + public static final String GENE_ID = "gene_id"; + public static final String TRANSCRIPT_ID = "transcript_id"; +} diff --git a/src/main/java/htsjdk/tribble/gff/GtfWriter.java b/src/main/java/htsjdk/tribble/gff/GtfWriter.java new file mode 100644 index 0000000000..39ccc47c97 --- /dev/null +++ b/src/main/java/htsjdk/tribble/gff/GtfWriter.java @@ -0,0 +1,52 @@ +package htsjdk.tribble.gff; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import htsjdk.samtools.util.FileExtensions; +import htsjdk.tribble.TribbleException; + + +/** + * A class to write out gtf files. + * and comments using {@link #addComment(String)}. + */ +public class GtfWriter extends AbstractGxxWriter { + + + public GtfWriter(final Path path) throws IOException { + super(path, FileExtensions.GTF); + } + + public GtfWriter(final OutputStream stream) { + super(stream); + } + + + @Override + protected void writeAttributes(final Map> attributes) throws IOException { + if (attributes.isEmpty()) { + out.write(GtfConstants.UNDEFINED_FIELD_VALUE.getBytes()); + } + + writeJoinedByDelimiter(GtfConstants.ATTRIBUTE_DELIMITER, e -> writeKeyValuePair(e.getKey(), e.getValue()), attributes.entrySet()); + } + + private void writeKeyValuePair(final String key, final List values) { + try { + tryToWrite(key); + out.write(GtfConstants.VALUE_DELIMITER); + writeJoinedByDelimiter(Gff3Constants.VALUE_DELIMITER, v -> tryToWrite(escapeString(v)), values); + } catch (final IOException ex) { + throw new TribbleException("error writing out key value pair " + key + " " + values); + } + } + +} \ No newline at end of file diff --git a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java new file mode 100644 index 0000000000..4a8ba5b594 --- /dev/null +++ b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java @@ -0,0 +1,72 @@ +package htsjdk.tribble.gff; + +import com.google.common.collect.ImmutableMap; +import htsjdk.HtsjdkTest; +import htsjdk.tribble.AbstractFeatureReader; +import htsjdk.tribble.TestUtils; +import htsjdk.tribble.TribbleException; +import htsjdk.tribble.annotation.Strand; +import htsjdk.tribble.readers.LineIterator; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +public class GtfCodecTest extends HtsjdkTest { + + final static String DATA_DIR = TestUtils.DATA_DIR + "/gff/"; + private final Path gencode47_gzipped = Paths.get(DATA_DIR + "gencode.v47.annotation.gtf.gz"); + + @DataProvider(name = "basicDecodeDataProvider") + Object[][] basicDecodeDataProvider() { + return new Object[][]{ + {gencode47_gzipped, 2} + }; + } + + @Test(dataProvider = "basicDecodeDataProvider") + public void basicDecodeTest(final Path inputGtf, final int expectedTotalFeatures) throws IOException { + Assert.assertTrue((new GtfCodec()).canDecode(inputGtf.toAbsolutePath().toString())); + final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(inputGtf.toAbsolutePath().toString(), null, new Gff3Codec(), false); + int countTotalFeatures = 0; + for (final Gff3Feature feature : reader.iterator()) { + countTotalFeatures++; + } + + Assert.assertEquals(countTotalFeatures, expectedTotalFeatures); + } + + @Test(dataProvider = "basicDecodeDataProvider") + public void codecFilterOutFieldsTest(final Path inputGtf, final int expectedTotalFeatures) throws IOException { + final Set skip_attributes = new HashSet<>(Arrays.asList("version","rank","biotype","transcript_support_level","mgi_id","havana_gene","tag")); + final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW, S->skip_attributes.contains(S)); + Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); + final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(inputGtf.toAbsolutePath().toString(), null,codec, false); + int countTotalFeatures = 0; + for (final Gff3Feature feature : reader.iterator()) { + for(final String key : skip_attributes) { + Assert.assertTrue(feature.getAttribute(key).isEmpty()); + Assert.assertFalse(feature.hasAttribute(key)); + Assert.assertFalse(feature.getUniqueAttribute(key).isPresent()); + } + countTotalFeatures++; + } + + Assert.assertEquals(countTotalFeatures, expectedTotalFeatures); + } + + + + +} \ No newline at end of file From 0c48c74a2a3dbf471d15e80409042a91cd34a66f Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Mon, 14 Apr 2025 19:36:32 +0200 Subject: [PATCH 3/7] cont --- .../htsjdk/tribble/gff/AbstractGxxCodec.java | 11 +- .../java/htsjdk/tribble/gff/Gff3Codec.java | 1 - .../java/htsjdk/tribble/gff/GtfCodec.java | 169 ++++++------ .../htsjdk/tribble/gff/Gff3CodecTest.java | 2 +- .../java/htsjdk/tribble/gff/GtfCodecTest.java | 134 +++++++--- .../htsjdk/tribble/gff/gencode.v47.PCSK9.gtf | 248 ++++++++++++++++++ .../tribble/gff/gencode.v47.annotation.gtf.gz | Bin 0 -> 14214 bytes .../gff/gencode.v47.annotation.gtf.gz.tbi | Bin 0 -> 289 bytes 8 files changed, 444 insertions(+), 121 deletions(-) create mode 100644 src/test/resources/htsjdk/tribble/gff/gencode.v47.PCSK9.gtf create mode 100644 src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz create mode 100644 src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz.tbi diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java index 2aef3f19bf..85c8a0990d 100644 --- a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java @@ -168,7 +168,7 @@ protected boolean canDecodeFirstLine(final String line) { strand.equals(Strand.NONE.toString()) || strand.equals("?"); } - + static String extractSingleAttribute(final List values) { if (values == null || values.isEmpty()) { return null; @@ -211,14 +211,7 @@ public final LocationAware makeIndexableSourceFromStream(final InputStream buffe public abstract boolean isDone(final LineIterator lineIterator); @Override - public void close(final LineIterator lineIterator) { - //cleanup resources - featuresToFlush.clear(); - activeFeaturesWithIDs.clear(); - activeFeatures.clear(); - activeParentIDs.clear(); - CloserUtil.close(lineIterator); - } + public abstract void close(final LineIterator lineIterator); @Override public final TabixFormat getTabixFormat() { diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java index d66128cb96..ac8bdd8d18 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java @@ -28,7 +28,6 @@ import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.Log; import htsjdk.tribble.TribbleException; -import htsjdk.tribble.annotation.Strand; import htsjdk.tribble.readers.LineIterator; import htsjdk.tribble.util.ParsingUtils; diff --git a/src/main/java/htsjdk/tribble/gff/GtfCodec.java b/src/main/java/htsjdk/tribble/gff/GtfCodec.java index de6e260571..ea985a4ff9 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfCodec.java +++ b/src/main/java/htsjdk/tribble/gff/GtfCodec.java @@ -17,11 +17,11 @@ import java.util.function.Predicate; import java.util.zip.GZIPInputStream; +import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.FileExtensions; import htsjdk.samtools.util.IOUtil; import htsjdk.samtools.util.Log; import htsjdk.tribble.TribbleException; -import htsjdk.tribble.gff.AbstractGxxCodec.DecodeDepth; import htsjdk.tribble.readers.LineIterator; /** @@ -67,7 +67,6 @@ private boolean isTranscript(final Gff3Feature feature) { } - @Override protected Gff3Feature decode(LineIterator lineIterator, DecodeDepth depth) throws IOException { currentLine++; @@ -136,7 +135,11 @@ protected Gff3Feature decode(LineIterator lineIterator, DecodeDepth depth) throw @Override protected Map> parseAttributesColumn(final String attributesString) throws UnsupportedEncodingException { - if (attributesString.equals(GtfConstants.UNDEFINED_FIELD_VALUE)) { + return parseAttributes(attributesString); + } + + static Map> parseAttributes(final String attributesString) throws UnsupportedEncodingException { + if (attributesString.trim().equals(GtfConstants.UNDEFINED_FIELD_VALUE)) { return Collections.emptyMap(); } final Map> attributes = new LinkedHashMap<>(); @@ -148,6 +151,7 @@ protected Map> parseAttributesColumn(final String attribute while (i < len && Character.isWhitespace(attributesString.charAt(i))) { i++; } + // end of string if (i >= len) { break; @@ -157,7 +161,11 @@ protected Map> parseAttributesColumn(final String attribute // consumme key while (i < len && !Character.isWhitespace(attributesString.charAt(i))) { - keyBuilder.append(attributesString.charAt(i)); + char c = attributesString.charAt(i); + if(c==Gff3Constants.KEY_VALUE_SEPARATOR) { + throw new TribbleException("unexpected gff3 separator "+Gff3Constants.KEY_VALUE_SEPARATOR+" in gtf line "+attributesString); + } + keyBuilder.append(c); i++; } // skip whitespaces @@ -167,98 +175,89 @@ protected Map> parseAttributesColumn(final String attribute final String key = keyBuilder.toString(); - + /* read VALUE */ + final StringBuilder valueBuilder = new StringBuilder(); + // no value if (i >= len) { logger.warn("no value for '" + key + "' in " + attributesString); - attributes.put(key,Collections.singletonList("")); - break; } - - final List values=new ArrayList<>(1); - // read multiple values - for(;;) { - /* read VALUE */ - final StringBuilder valueBuilder = new StringBuilder(); + else + { // first char of value char c = attributesString.charAt(i); - + if (c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { // no value - i++; - values.add(""); - logger.warn("no value for '" + key + "' in " + attributesString); - break; + logger.warn("no value for '" + key + "' in " + attributesString); + } + else if(c==Gff3Constants.KEY_VALUE_SEPARATOR) { + throw new TribbleException("unexpected gff3 separator '"+Gff3Constants.KEY_VALUE_SEPARATOR+"' in gtf line "+attributesString); } - - // quoted string - if (c == '\"') { + else if (c == '\"' || c=='\'') { + final char quote_symbol = c; i++; - while (i < len) { + for(;;) { + if(i>=len) throw new TribbleException("unclosed quoted string in "+attributesString); c = attributesString.charAt(i); - ++i; if (c == '\\') { - c = (i < len ? attributesString.charAt(i) : '\0'); + if(i+1>=len) throw new TribbleException("unclosed escape symbol in "+attributesString); ++i; + c = attributesString.charAt(i); switch (c) { - case '"': - valueBuilder.append("\""); - break; - case '\'': - valueBuilder.append("\'"); - break; - case 't': - valueBuilder.append("\t"); - break; - case 'n': - valueBuilder.append("\n"); - break; - default: - //logger.warn("unparsed value in " + attributesString); - break; + case '"': valueBuilder.append("\""); break; + case '\'': valueBuilder.append("\'"); break; + case 't': valueBuilder.append("\t"); break; + case 'n': valueBuilder.append("\n"); break; + default: logger.warn("unparsed escape symbol in " + attributesString); break; } - } else if (c == '\"') { - values.add(valueBuilder.toString()); + i++; + } else if (c ==quote_symbol) { + i++; break; } else { valueBuilder.append(c); + i++; } - } // end of while - - - + } // end of for } - else /* not a quoted string */ - { - while (i < len) { - c = attributesString.charAt(i); - ++i; - if (Character.isWhitespace(c) || c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { - break; - } - valueBuilder.append(c); - } - values.add(valueBuilder.toString()); - } + else /* not a quoted string, for example, a number */ + { + while (i < len) { + c = attributesString.charAt(i); + if(c==Gff3Constants.KEY_VALUE_SEPARATOR) { + throw new TribbleException("unexpected gff3 separator '"+Gff3Constants.KEY_VALUE_SEPARATOR+"' in gtf line "+attributesString); + } + if (Character.isWhitespace(c) || c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { + break; + } + valueBuilder.append(c); + i++; + } + } + }// end of else 'value' + + List values = attributes.get(key); + if(values==null) { + values = new ArrayList<>(); + attributes.put(key,values); + } + values.add(valueBuilder.toString()); - // skip whitespaces - while (i < len && Character.isWhitespace(attributesString.charAt(i))) { - i++; - } - if (i >= len) { - break; - } - - if(attributesString.charAt(i)==AbstractGxxConstants.ATTRIBUTE_DELIMITER) { - i++; - break; - } - }//end of read multiple values - attributes.put(key, values); - } + // skip whitespaces + while (i < len && Character.isWhitespace(attributesString.charAt(i))) { + i++; + } + if (i >= len) { + break; + } + if(attributesString.charAt(i)!=AbstractGxxConstants.ATTRIBUTE_DELIMITER) { + throw new TribbleException("expected a '"+AbstractGxxConstants.ATTRIBUTE_DELIMITER+"' in gtf line before : \""+attributesString.substring(i)+"\". "+attributes); + } + i++; + } return attributes; - - } + } @Override public boolean canDecode(final String inputFilePath) { @@ -335,10 +334,26 @@ private void prepareToFlushFeatures() { activeUncharacterized.clear(); } - + + + @Override public boolean isDone(LineIterator lineIterator) { - // TODO Auto-generated method stub - return false; + return !lineIterator.hasNext() && + activeGenes.isEmpty() && + activeTranscriptComponents.isEmpty() && + activeTranscripts.isEmpty() && + activeUncharacterized.isEmpty() && + featuresToFlush.isEmpty(); } + + @Override + public void close(LineIterator source) { + activeGenes.clear(); + activeTranscriptComponents.clear(); + activeTranscripts.clear(); + activeUncharacterized.clear(); + featuresToFlush.clear(); + CloserUtil.close(source); + } } diff --git a/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java b/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java index 38b9fb5d9c..9345f03947 100644 --- a/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java +++ b/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java @@ -543,4 +543,4 @@ public void extractSingleAttributeTest(final List attributes, final Stri Assert.assertEquals(singleAttribute, expectedSingleAttribute); } } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java index 4a8ba5b594..ae8f243c08 100644 --- a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java +++ b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java @@ -1,58 +1,80 @@ package htsjdk.tribble.gff; -import com.google.common.collect.ImmutableMap; -import htsjdk.HtsjdkTest; -import htsjdk.tribble.AbstractFeatureReader; -import htsjdk.tribble.TestUtils; -import htsjdk.tribble.TribbleException; -import htsjdk.tribble.annotation.Strand; -import htsjdk.tribble.readers.LineIterator; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import htsjdk.HtsjdkTest; +import htsjdk.samtools.util.CloseableIterator; +import htsjdk.tribble.AbstractFeatureReader; +import htsjdk.tribble.TestUtils; +import htsjdk.tribble.annotation.Strand; +import htsjdk.tribble.readers.LineIterator; public class GtfCodecTest extends HtsjdkTest { final static String DATA_DIR = TestUtils.DATA_DIR + "/gff/"; private final Path gencode47_gzipped = Paths.get(DATA_DIR + "gencode.v47.annotation.gtf.gz"); + private final Path gencode47_PCSK9 = Paths.get(DATA_DIR + "gencode.v47.PCSK9.gtf"); @DataProvider(name = "basicDecodeDataProvider") Object[][] basicDecodeDataProvider() { return new Object[][]{ - {gencode47_gzipped, 2} + {gencode47_gzipped, 2,0}, + {gencode47_PCSK9, 1, 248} }; } - @Test(dataProvider = "basicDecodeDataProvider") - public void basicDecodeTest(final Path inputGtf, final int expectedTotalFeatures) throws IOException { - Assert.assertTrue((new GtfCodec()).canDecode(inputGtf.toAbsolutePath().toString())); - final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(inputGtf.toAbsolutePath().toString(), null, new Gff3Codec(), false); - int countTotalFeatures = 0; - for (final Gff3Feature feature : reader.iterator()) { - countTotalFeatures++; - } + private void basicDecodeTest(final Path inputGtf, GtfCodec.DecodeDepth decodeDepth, final int expectedCount) throws IOException { + final GtfCodec codec = new GtfCodec(decodeDepth); + Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); + + try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( + DATA_DIR + "Homo_sapiens.GRCh38.97.chromosome.1.small.gff3.gz", null, new Gff3Codec(decodeDepth), false)) { + for (final Gff3Feature feature : reader.iterator()) { + + } + } + + + try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( + inputGtf.toAbsolutePath().toString(), null, codec, false)) { + int countTotalFeatures = 0; + for (final Gff3Feature feature : reader.iterator()) { + countTotalFeatures++; + } + Assert.assertEquals(countTotalFeatures, expectedCount); + } + } - Assert.assertEquals(countTotalFeatures, expectedTotalFeatures); + + @Test(dataProvider = "basicDecodeDataProvider") + public void basicDecodeDeepTest(final Path inputGtf, final int expectedCountDeep, final int _ignore) throws IOException { + basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.DEEP,expectedCountDeep); } @Test(dataProvider = "basicDecodeDataProvider") - public void codecFilterOutFieldsTest(final Path inputGtf, final int expectedTotalFeatures) throws IOException { - final Set skip_attributes = new HashSet<>(Arrays.asList("version","rank","biotype","transcript_support_level","mgi_id","havana_gene","tag")); + public void basicDecodeShallowTest(final Path inputGtf, final int _ignore, final int expectedCountShallow) throws IOException { + basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.SHALLOW,expectedCountShallow); + } + + @Test(dataProvider = "basicDecodeDataProvider") + public void codecFilterOutFieldsTest(final Path inputGtf, final int _ignore,int expectedTotalFeatures) throws IOException { + final Set skip_attributes = new HashSet<>(Arrays.asList("tag","havana_gene")); final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW, S->skip_attributes.contains(S)); Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); - final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(inputGtf.toAbsolutePath().toString(), null,codec, false); + AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( + inputGtf.toAbsolutePath().toString(), null,codec, false); int countTotalFeatures = 0; for (final Gff3Feature feature : reader.iterator()) { for(final String key : skip_attributes) { @@ -60,13 +82,59 @@ public void codecFilterOutFieldsTest(final Path inputGtf, final int expectedTota Assert.assertFalse(feature.hasAttribute(key)); Assert.assertFalse(feature.getUniqueAttribute(key).isPresent()); } - countTotalFeatures++; - } - + countTotalFeatures++; + } Assert.assertEquals(countTotalFeatures, expectedTotalFeatures); } + public void FeatureContentTest() throws IOException { + final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW); + try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(gencode47_PCSK9.toAbsolutePath().toString(), null,codec, false)) { + try(CloseableIterator iter = reader.iterator()) { + Assert.assertTrue(iter.hasNext()); + Gff3Feature feature = iter.next(); + Assert.assertNotNull(feature); + Assert.assertEquals(feature.getContig(),"chr1"); + Assert.assertEquals(feature.getSource(),"HAVANA"); + Assert.assertEquals(feature.getType(),"UTR"); + Assert.assertEquals(feature.getStart(),55039445); + Assert.assertEquals(feature.getEnd(),5503983); + Assert.assertEquals(feature.getScore(),-1); + Assert.assertEquals(feature.getStrand(),Strand.POSITIVE); + Assert.assertEquals(feature.getPhase(),-1); + Assert.assertEquals(feature.getUniqueAttribute(GtfConstants.GENE_ID).get(),"ENSG00000169174.13"); + Assert.assertEquals(feature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get(),"ENST00000713785.1"); + Assert.assertEquals(feature.getUniqueAttribute("gene_type").get(),"protein_coding"); + Assert.assertEquals(feature.getUniqueAttribute("gene_name").get(),"PCSK9"); + Assert.assertEquals(feature.getUniqueAttribute("transcript_type").get(),"nonsense_mediated_decay"); + Assert.assertEquals(feature.getUniqueAttribute("transcript_name").get(),"PCSK9-208"); + Assert.assertEquals(feature.getUniqueAttribute("exon_number").get(),"1"); + Assert.assertEquals(feature.getUniqueAttribute("exon_id").get(),"ENSE00004011055.2"); + Assert.assertEquals(feature.getUniqueAttribute("level").get(),"2"); + Assert.assertEquals(feature.getUniqueAttribute("protein_id").get(),"ENSP00000519087.1"); + Assert.assertEquals(feature.getUniqueAttribute("hgnc_id").get(),"HGNC:20001"); + Assert.assertEquals(feature.getUniqueAttribute("havana_gene").get(),"OTTHUMG00000008136.2"); + Assert.assertFalse(feature.getUniqueAttribute("zz").isPresent()); + Assert.assertTrue(iter.hasNext()); + } + } + } - - -} \ No newline at end of file + public void decodeFeaturesTest() throws IOException { + String s = GtfConstants.UNDEFINED_FIELD_VALUE; + Map> h = GtfCodec.parseAttributes(s); + Assert.assertTrue(h.isEmpty()); + + h = GtfCodec.parseAttributes("key1 \"ABCD\"; key2 12345 ; key3 'hello'"); + Assert.assertTrue(h.isEmpty()); + for(String k: h.keySet()) { + Assert.assertEquals(h.get(k).size(),1); + } + Assert.assertEquals(h.get("key1").get(0),"ABCD"); + Assert.assertEquals(h.get("key2").get(0),"12345"); + Assert.assertEquals(h.get("key3").get(0),"hello"); + + + } + +} diff --git a/src/test/resources/htsjdk/tribble/gff/gencode.v47.PCSK9.gtf b/src/test/resources/htsjdk/tribble/gff/gencode.v47.PCSK9.gtf new file mode 100644 index 0000000000..7c75532e86 --- /dev/null +++ b/src/test/resources/htsjdk/tribble/gff/gencode.v47.PCSK9.gtf @@ -0,0 +1,248 @@ +chr1 HAVANA UTR 55039445 55039837 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 1; exon_id "ENSE00004011055.2"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55039445 55040044 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 1; exon_id "ENSE00004011055.2"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA gene 55039445 55064852 . + . gene_id "ENSG00000169174.13"; gene_type "protein_coding"; gene_name "PCSK9"; level 2; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA transcript 55039445 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55039447 55039837 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 1; exon_id "ENSE00003897031.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55039447 55040044 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 1; exon_id "ENSE00003897031.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA transcript 55039447 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55039456 55039480 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 1; exon_id "ENSE00001884398.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55039456 55040044 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 1; exon_id "ENSE00001884398.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA transcript 55039456 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55039462 55039837 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 1; exon_id "ENSE00004013014.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55039462 55040044 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 1; exon_id "ENSE00004013014.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA transcript 55039462 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA UTR 55039468 55039837 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 1; exon_id "ENSE00004021263.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55039468 55040044 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 1; exon_id "ENSE00004021263.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA transcript 55039468 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55039481 55040044 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 1; exon_id "ENSE00001884398.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA start_codon 55039481 55039483 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 1; exon_id "ENSE00001884398.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55039548 55039837 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 1; exon_id "ENSE00004021261.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55039548 55040044 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 1; exon_id "ENSE00004021261.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA transcript 55039548 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA UTR 55039550 55039837 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 1; exon_id "ENSE00004021267.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55039550 55040044 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 1; exon_id "ENSE00004021267.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA transcript 55039550 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55039838 55040044 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 1; exon_id "ENSE00004021261.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55039838 55040044 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 1; exon_id "ENSE00003897031.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA CDS 55039838 55040044 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 1; exon_id "ENSE00004013014.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55039838 55040044 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 1; exon_id "ENSE00004011055.2"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55039838 55040044 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 1; exon_id "ENSE00004021263.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55039838 55040044 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 1; exon_id "ENSE00004021267.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA start_codon 55039838 55039840 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 1; exon_id "ENSE00004021261.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA start_codon 55039838 55039840 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 1; exon_id "ENSE00003897031.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA start_codon 55039838 55039840 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 1; exon_id "ENSE00004013014.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA start_codon 55039838 55039840 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 1; exon_id "ENSE00004011055.2"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA start_codon 55039838 55039840 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 1; exon_id "ENSE00004021263.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA start_codon 55039838 55039840 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 1; exon_id "ENSE00004021267.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55040295 55040403 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 1; exon_id "ENSE00003897575.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55040295 55040403 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 1; exon_id "ENSE00003897575.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA transcript 55040295 55064797 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55041967 55042089 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 2; exon_id "ENSE00004021272.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55041967 55042089 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 2; exon_id "ENSE00004021272.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55043843 55044034 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55043843 55044034 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA CDS 55043843 55044034 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55043843 55044034 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55043843 55044034 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 3; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55043843 55044034 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55043843 55044010 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 2; exon_id "ENSE00003896896.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55043843 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55043843 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55043843 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 2; exon_id "ENSE00003896896.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55043843 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55043843 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55043843 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 3; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55043843 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 2; exon_id "ENSE00001279167.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55043966 55044034 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673662.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding_CDS_not_defined"; transcript_name "PCSK9-203"; exon_number 1; exon_id "ENSE00003897988.1"; level 2; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530153.1"; +chr1 HAVANA transcript 55043966 55047332 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673662.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding_CDS_not_defined"; transcript_name "PCSK9-203"; level 2; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530153.1"; +chr1 HAVANA CDS 55044011 55044034 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 2; exon_id "ENSE00003896896.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA start_codon 55044011 55044013 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 2; exon_id "ENSE00003896896.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55046523 55046646 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55046523 55046646 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA CDS 55046523 55046646 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55046523 55046646 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55046523 55046646 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55046523 55046646 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 4; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55046523 55046646 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673662.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding_CDS_not_defined"; transcript_name "PCSK9-203"; exon_number 2; exon_id "ENSE00003896521.1"; level 2; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530153.1"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 4; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55046523 55046646 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 3; exon_id "ENSE00001279161.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55046923 55047332 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673662.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding_CDS_not_defined"; transcript_name "PCSK9-203"; exon_number 3; exon_id "ENSE00003896780.1"; level 2; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530153.1"; +chr1 HAVANA exon 55050934 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 1; exon_id "ENSE00001850558.1"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA transcript 55050934 55064850 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA CDS 55051343 55051431 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 4; exon_id "ENSE00003897734.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55051343 55051453 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 4; exon_id "ENSE00003897734.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55051432 55051453 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 4; exon_id "ENSE00003897734.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA stop_codon 55051432 55051434 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 4; exon_id "ENSE00003897734.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA CDS 55052278 55052307 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 2; exon_id "ENSE00003646938.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55052278 55052411 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55052278 55052411 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55052278 55052411 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55052278 55052411 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55052278 55052411 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 5; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55052278 55052411 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 5; exon_id "ENSE00003897397.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 5; exon_id "ENSE00003897397.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 2; exon_id "ENSE00003646938.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 5; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55052278 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 4; exon_id "ENSE00004021268.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55052308 55052411 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 2; exon_id "ENSE00003646938.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA stop_codon 55052308 55052310 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 2; exon_id "ENSE00003646938.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55052650 55052791 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55052650 55052791 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55052650 55052791 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55052650 55052791 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55052650 55052791 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 6; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55052650 55052791 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 6; exon_id "ENSE00003683038.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 3; exon_id "ENSE00003683038.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 2; exon_id "ENSE00003683038.1"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 6; exon_id "ENSE00003683038.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 3; exon_id "ENSE00003683038.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 6; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55052650 55052791 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 5; exon_id "ENSE00004021270.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55055993 55056189 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55055993 55056189 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55055993 55056189 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55055993 55056189 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55055993 55056189 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 7; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55055993 55056189 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 7; exon_id "ENSE00003626397.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 4; exon_id "ENSE00003626397.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 3; exon_id "ENSE00003626397.1"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 7; exon_id "ENSE00003626397.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 4; exon_id "ENSE00003626397.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 7; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55055993 55056189 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 6; exon_id "ENSE00004021269.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55056537 55056641 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 7; exon_id "ENSE00004021271.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55056537 55056805 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 7; exon_id "ENSE00004021271.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55056642 55056805 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 7; exon_id "ENSE00004021271.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA stop_codon 55056642 55056644 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 7; exon_id "ENSE00004021271.2"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55057331 55057514 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55057331 55057514 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55057331 55057514 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55057331 55057514 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55057331 55057514 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 8; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 8; exon_id "ENSE00001156685.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 5; exon_id "ENSE00001156685.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 8; exon_id "ENSE00001156685.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 8; exon_id "ENSE00001156685.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 7; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 5; exon_id "ENSE00001156685.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 8; exon_id "ENSE00004021262.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55057331 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 8; exon_id "ENSE00001156685.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55057428 55057514 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 4; exon_id "ENSE00001907083.1"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA CDS 55058036 55058209 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55058036 55058209 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55058036 55058209 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55058036 55058209 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55058036 55058209 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 9; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 9; exon_id "ENSE00003509095.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 6; exon_id "ENSE00003509095.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 9; exon_id "ENSE00003509095.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 5; exon_id "ENSE00003509095.1"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 9; exon_id "ENSE00003509095.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 8; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 6; exon_id "ENSE00003509095.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 9; exon_id "ENSE00004021266.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058036 55058209 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 9; exon_id "ENSE00003509095.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55058499 55058647 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55058499 55058647 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55058499 55058647 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55058499 55058647 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55058499 55058647 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 10; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 10; exon_id "ENSE00003602007.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 7; exon_id "ENSE00003602007.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 10; exon_id "ENSE00003602007.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 6; exon_id "ENSE00003602007.1"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 10; exon_id "ENSE00003602007.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 9; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 7; exon_id "ENSE00003602007.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 10; exon_id "ENSE00004021274.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55058499 55058647 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 10; exon_id "ENSE00003602007.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55059486 55059663 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55059486 55059663 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55059486 55059663 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55059486 55059663 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55059486 55059663 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 11; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 11; exon_id "ENSE00003683046.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 8; exon_id "ENSE00003683046.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 11; exon_id "ENSE00003683046.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 11; exon_id "ENSE00003683046.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 10; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 8; exon_id "ENSE00003683046.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 11; exon_id "ENSE00004021273.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55059486 55059663 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 11; exon_id "ENSE00003683046.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55061265 55061350 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 11; exon_id "ENSE00003897219.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55061265 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 11; exon_id "ENSE00003897219.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA UTR 55061351 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 11; exon_id "ENSE00003897219.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA stop_codon 55061351 55061353 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 11; exon_id "ENSE00003897219.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA CDS 55061375 55061556 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 11; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55061375 55061556 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 11; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55061375 55061556 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 11; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55061375 55061556 . + 2 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 12; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 12; exon_id "ENSE00003645672.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 9; exon_id "ENSE00003645672.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 12; exon_id "ENSE00003645672.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 11; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 7; exon_id "ENSE00003645672.1"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 12; exon_id "ENSE00003645672.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 11; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 11; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 9; exon_id "ENSE00003645672.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 12; exon_id "ENSE00004021265.2"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55061375 55061556 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 12; exon_id "ENSE00003645672.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55063369 55063581 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA CDS 55063369 55063581 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 12; exon_id "ENSE00003897035.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA CDS 55063369 55063581 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA CDS 55063369 55063581 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 13; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 13; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA UTR 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 12; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA UTR 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 10; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 13; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55063369 55064797 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 12; exon_id "ENSE00003897035.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA exon 55063369 55064850 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000490692.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "retained_intron"; transcript_name "PCSK9-202"; exon_number 8; exon_id "ENSE00001427326.3"; level 2; transcript_support_level "2"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022279.1"; +chr1 HAVANA exon 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA exon 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673726.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-204"; exon_number 13; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000501004.1"; hgnc_id "HGNC:20001"; tag "inferred_exon_combination"; tag "RNA_Seq_supported_only"; tag "upstream_ATG"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530151.1"; +chr1 HAVANA exon 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673913.2"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-206"; exon_number 12; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000501161.2"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530154.1"; +chr1 HAVANA exon 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713785.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-208"; exon_number 10; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000519087.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 13; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA exon 55063369 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713787.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "nonsense_mediated_decay"; transcript_name "PCSK9-210"; exon_number 13; exon_id "ENSE00001922690.1"; level 2; protein_id "ENSP00000519089.1"; hgnc_id "HGNC:20001"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55063582 55064797 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 12; exon_id "ENSE00003897035.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA UTR 55063582 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA UTR 55063582 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA UTR 55063582 55064852 . + . gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 13; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA stop_codon 55063582 55063584 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000302118.5"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-201"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000303208.5"; transcript_support_level "1"; hgnc_id "HGNC:20001"; tag "basic"; tag "Ensembl_canonical"; tag "GENCODE_Primary"; tag "MANE_Select"; tag "appris_principal_2"; tag "CCDS"; ccdsid "CCDS603.1"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000022280.2"; +chr1 HAVANA stop_codon 55063582 55063584 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000673903.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-205"; exon_number 12; exon_id "ENSE00003897035.1"; level 2; protein_id "ENSP00000501257.1"; hgnc_id "HGNC:20001"; tag "RNA_Seq_supported_only"; tag "basic"; havana_gene "OTTHUMG00000008136.2"; havana_transcript "OTTHUMT00000530152.1"; +chr1 HAVANA stop_codon 55063582 55063584 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000710286.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-207"; exon_number 12; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000518176.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; +chr1 HAVANA stop_codon 55063582 55063584 . + 0 gene_id "ENSG00000169174.13"; transcript_id "ENST00000713786.1"; gene_type "protein_coding"; gene_name "PCSK9"; transcript_type "protein_coding"; transcript_name "PCSK9-209"; exon_number 13; exon_id "ENSE00004021264.1"; level 2; protein_id "ENSP00000519088.1"; hgnc_id "HGNC:20001"; tag "basic"; tag "appris_alternative_2"; havana_gene "OTTHUMG00000008136.2"; diff --git a/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz b/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz new file mode 100644 index 0000000000000000000000000000000000000000..4601f3060560edcb8037afb3398d795ecab26964 GIT binary patch literal 14214 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 B2H*ey literal 0 HcmV?d00001 diff --git a/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz.tbi b/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz.tbi new file mode 100644 index 0000000000000000000000000000000000000000..aa6ea43d64e7a32ddf4c0dd7f770f641160c63c4 GIT binary patch literal 289 zcmb2|=3rp}f&Xj_PR>jWER1jO8hSB1im*L6>DJ9s#3tykpme({HmJ*dL#G+z@7%Sj zxwjND_d4lK%2t_cJX11h&9?8=AMPFBSIb@9{Qqa6Rff|#$2Z+Krf_RgELPt|jh z3K8f{_E{G%OD|LZmCrOdyla2xV}_Wbyp{l50ue;G{7 VUHG?cW?+y<3j}EfW^kB*2mmk=ca8u6 literal 0 HcmV?d00001 From 6a7e0ab03fe3e664dc4af8f87230fd8601e7d2cf Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Mon, 14 Apr 2025 19:50:21 +0200 Subject: [PATCH 4/7] cont --- .../java/htsjdk/tribble/gff/GtfCodecTest.java | 2 +- .../tribble/gff/gencode.v47.annotation.gtf.gz | Bin 14214 -> 10144 bytes .../gff/gencode.v47.annotation.gtf.gz.tbi | Bin 289 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz.tbi diff --git a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java index ae8f243c08..c3ed73f294 100644 --- a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java +++ b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java @@ -30,7 +30,7 @@ public class GtfCodecTest extends HtsjdkTest { @DataProvider(name = "basicDecodeDataProvider") Object[][] basicDecodeDataProvider() { return new Object[][]{ - {gencode47_gzipped, 2,0}, + {gencode47_gzipped, 2, 842}, {gencode47_PCSK9, 1, 248} }; } diff --git a/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz b/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz index 4601f3060560edcb8037afb3398d795ecab26964..3a0f7aba7f120192c23e3a61fa5cde672a593bcb 100644 GIT binary patch literal 10144 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 HKGy#L_VlOY literal 14214 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 B2H*ey diff --git a/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz.tbi b/src/test/resources/htsjdk/tribble/gff/gencode.v47.annotation.gtf.gz.tbi deleted file mode 100644 index aa6ea43d64e7a32ddf4c0dd7f770f641160c63c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmb2|=3rp}f&Xj_PR>jWER1jO8hSB1im*L6>DJ9s#3tykpme({HmJ*dL#G+z@7%Sj zxwjND_d4lK%2t_cJX11h&9?8=AMPFBSIb@9{Qqa6Rff|#$2Z+Krf_RgELPt|jh z3K8f{_E{G%OD|LZmCrOdyla2xV}_Wbyp{l50ue;G{7 VUHG?cW?+y<3j}EfW^kB*2mmk=ca8u6 From 2802135421480bcefeb73baae2c828310da92c09 Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Tue, 15 Apr 2025 00:30:54 +0200 Subject: [PATCH 5/7] cont --- .../htsjdk/tribble/gff/AbstractGxxCodec.java | 4 +- .../htsjdk/tribble/gff/AbstractGxxWriter.java | 22 ++- .../java/htsjdk/tribble/gff/Gff3BaseData.java | 7 + .../java/htsjdk/tribble/gff/Gff3Codec.java | 3 - .../htsjdk/tribble/gff/Gff3FeatureImpl.java | 8 + .../java/htsjdk/tribble/gff/Gff3Writer.java | 2 +- .../java/htsjdk/tribble/gff/GtfCodec.java | 95 ++++++------ .../java/htsjdk/tribble/gff/GtfWriter.java | 34 ++--- .../htsjdk/tribble/gff/Gff3WriterTest.java | 15 +- .../java/htsjdk/tribble/gff/GtfCodecTest.java | 70 ++++++--- .../htsjdk/tribble/gff/GtfWriterTest.java | 144 ++++++++++++++++++ 11 files changed, 302 insertions(+), 102 deletions(-) create mode 100644 src/test/java/htsjdk/tribble/gff/GtfWriterTest.java diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java index 85c8a0990d..104dabde17 100644 --- a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java @@ -7,15 +7,12 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Queue; -import java.util.Set; import java.util.function.Predicate; -import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.LocationAware; import htsjdk.tribble.AbstractFeatureCodec; import htsjdk.tribble.Feature; @@ -49,6 +46,7 @@ public abstract class AbstractGxxCodec extends AbstractFeatureCodec activeFeatures = new ArrayDeque<>(); protected final Queue featuresToFlush = new ArrayDeque<>(); protected final Map commentsWithLineNumbers = new LinkedHashMap<>(); diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java index dbbf5c84d0..4964870405 100644 --- a/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java @@ -1,6 +1,7 @@ package htsjdk.tribble.gff; import htsjdk.samtools.util.BlockCompressedOutputStream; +import htsjdk.samtools.util.FileExtensions; import htsjdk.samtools.util.IOUtil; import htsjdk.tribble.TribbleException; @@ -88,8 +89,8 @@ protected final void writeJoinedByDelimiter(final char delimiter, final Cons * @param feature the feature to be added * @throws IOException */ - public final void addFeature(final Gff3Feature feature) throws IOException { - writeFirstEightFields(feature); + public void addFeature(final Gff3Feature feature) throws IOException { + writeFirstEightFields(feature); out.write(AbstractGxxConstants.FIELD_DELIMITER); writeAttributes(feature.getAttributes()); out.write(AbstractGxxConstants.END_OF_LINE_CHARACTER); @@ -129,4 +130,19 @@ public final void addComment(final String comment) throws IOException { public final void close() throws IOException { out.close(); } -} \ No newline at end of file + + /** opens a writer as a Gff3Writer or as a GtfWriter using the path suffix */ + public static AbstractGxxWriter openWithFileExtension(final Path path) throws IOException { + if (FileExtensions.GFF3.stream().anyMatch(e -> path.toString().endsWith(e))) { + return new Gff3Writer(path); + } + else if (FileExtensions.GTF.stream().anyMatch(e -> path.toString().endsWith(e))) { + return new GtfWriter(path); + } + else + { + throw new IllegalArgumentException(path.toString()+" doesn't have a gtf or a gff3 suffix"); + } + } + +} diff --git a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java index 8d35978860..6b31e5744e 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java @@ -207,4 +207,11 @@ public String getName() { public List getAliases() { return aliases; } + + @Override + public String toString() { + return "Gff3BaseData [contig=" + contig + ", source=" + source + ", type=" + type + ", start=" + start + + ", end=" + end + ", score=" + score + ", strand=" + strand + ", phase=" + phase + ", attributes=" + + attributes + "]"; + } } diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java index ac8bdd8d18..049b8c2d3c 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java @@ -9,7 +9,6 @@ import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -17,7 +16,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Queue; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -46,7 +44,6 @@ public class Gff3Codec extends AbstractGxxCodec { private static final String ARTEMIS_FASTA_MARKER = ">"; - protected final Queue activeFeatures = new ArrayDeque<>(); private final Map> activeFeaturesWithIDs = new HashMap<>(); private final Map> activeParentIDs = new HashMap<>(); private final Map sequenceRegionMap = new LinkedHashMap<>(); diff --git a/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java b/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java index 9ac33360fa..6a7de7a447 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java @@ -313,4 +313,12 @@ public int hashCode() { } } + @Override + public String toString() { + return "Gff3FeatureImpl [baseData=" + baseData + + " , parents:" + this.parents.size() + + " , children:" + this.children.size() + + "]"; + } + } \ No newline at end of file diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Writer.java b/src/main/java/htsjdk/tribble/gff/Gff3Writer.java index 120fb9809e..ba5139e038 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Writer.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Writer.java @@ -36,7 +36,7 @@ private void initialize() { } } - + @Override protected void writeAttributes(final Map> attributes) throws IOException { if (attributes.isEmpty()) { diff --git a/src/main/java/htsjdk/tribble/gff/GtfCodec.java b/src/main/java/htsjdk/tribble/gff/GtfCodec.java index ea985a4ff9..b56b880060 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfCodec.java +++ b/src/main/java/htsjdk/tribble/gff/GtfCodec.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -31,11 +32,9 @@ public class GtfCodec extends AbstractGxxCodec { private final static Log logger = Log.getInstance(GtfCodec.class); - private final Map activeGenes = new HashMap<>(); - private final Map activeTranscripts = new HashMap<>(); - private final Map> activeTranscriptComponents = new HashMap<>(); - private final List activeUncharacterized = new ArrayList<>(); - + private final Map id2gene = new HashMap<>(); + private final Map id2transcripts = new HashMap<>(); + private final Map> id2TranscriptComponents = new HashMap<>(); public GtfCodec() { this(DecodeDepth.DEEP); @@ -91,9 +90,10 @@ protected Gff3Feature decode(LineIterator lineIterator, DecodeDepth depth) throw final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, super.filterOutAttribute)); + super.activeFeatures.add(thisFeature); + switch(depth) { case SHALLOW: - this.activeUncharacterized.add(thisFeature); //flush all features immediatly prepareToFlushFeatures(); break; @@ -102,30 +102,28 @@ protected Gff3Feature decode(LineIterator lineIterator, DecodeDepth depth) throw final String gene_id = thisFeature.getUniqueAttribute(GtfConstants.GENE_ID).orElseThrow(()-> new TribbleException("attribute "+GtfConstants.GENE_ID+" missing in "+line) ); - if(activeGenes.containsKey(gene_id)) { + if(id2gene.containsKey(gene_id)) { throw new TribbleException("duplicate gene "+ gene_id +" in "+line); } - activeGenes.put(gene_id, thisFeature); + id2gene.put(gene_id, thisFeature); } else if(isTranscript(thisFeature)) { final String transcript_id = thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+line) ); - if(activeTranscripts.containsKey(transcript_id)) { + if(id2transcripts.containsKey(transcript_id)) { throw new TribbleException("duplicate transcript "+ transcript_id +" in "+line); } - activeTranscripts.put(transcript_id, thisFeature); + id2transcripts.put(transcript_id, thisFeature); } else if(thisFeature.hasAttribute(GtfConstants.TRANSCRIPT_ID)){ final String transcript_id = thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+line) ); - List components =this.activeTranscriptComponents.get(transcript_id); + List components =this.id2TranscriptComponents.get(transcript_id); if(components==null) { components = new ArrayList<>(); - this.activeTranscriptComponents.put(transcript_id, components); + this.id2TranscriptComponents.put(transcript_id, components); } components.add(thisFeature); - } else { - this.activeUncharacterized.add(thisFeature); } break; } @@ -173,7 +171,7 @@ static Map> parseAttributes(final String attributesString) i++; } - final String key = keyBuilder.toString(); + final String key = URLDecoder.decode(keyBuilder.toString().trim(), "UTF-8"); /* read VALUE */ final StringBuilder valueBuilder = new StringBuilder(); @@ -242,7 +240,7 @@ else if (c == '\"' || c=='\'') { values = new ArrayList<>(); attributes.put(key,values); } - values.add(valueBuilder.toString()); + values.add( URLDecoder.decode(valueBuilder.toString(), "UTF-8")); // skip whitespaces while (i < len && Character.isWhitespace(attributesString.charAt(i))) { @@ -303,35 +301,34 @@ public boolean canDecode(final String inputFilePath) { */ private void prepareToFlushFeatures() { - activeTranscripts.values().stream().forEach(FEAT->{ + this.id2transcripts.values().stream().forEach(FEAT->{ final String gene_id = FEAT.getUniqueAttribute(GtfConstants.GENE_ID).orElseThrow(()-> - new TribbleException("attribute "+GtfConstants.GENE_ID+" missing in "+FEAT) - ); - final Gff3FeatureImpl geneFeat = activeGenes.get(gene_id); - if(!activeGenes.containsKey(gene_id)) { - throw new TribbleException("undefined gene "+ gene_id +" in "+FEAT); - } - FEAT.addParent(geneFeat); - }); + new TribbleException("attribute "+GtfConstants.GENE_ID+" missing in "+FEAT) + ); + if(!id2gene.containsKey(gene_id)) { + throw new TribbleException("undefined gene "+ gene_id +" in "+FEAT); + } + final Gff3FeatureImpl geneFeat = id2gene.get(gene_id); + FEAT.addParent(geneFeat); + }); - activeTranscriptComponents.values().stream().flatMap(L->L.stream()).forEach(FEAT->{ + this.id2TranscriptComponents.values().stream().flatMap(L->L.stream()).forEach(FEAT->{ final String transcript_id = FEAT.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+FEAT) - ); - final Gff3FeatureImpl transcriptFeat = activeTranscripts.get(transcript_id); - if(!activeTranscripts.containsKey(transcript_id)) { - throw new TribbleException("undefined transcript_id "+ transcript_id +" in "+FEAT); - } - FEAT.addParent(transcriptFeat); - }); - + ); + if(!id2transcripts.containsKey(transcript_id)) { + throw new TribbleException("undefined transcript_id "+ transcript_id +" in "+FEAT); + } + final Gff3FeatureImpl transcriptFeat = id2transcripts.get(transcript_id); + FEAT.addParent(transcriptFeat); + }); + - featuresToFlush.addAll(activeGenes.values()); - featuresToFlush.addAll(activeUncharacterized); - activeTranscripts.clear(); - activeTranscriptComponents.clear(); - activeGenes.clear(); - activeUncharacterized.clear(); + featuresToFlush.addAll(activeFeatures); + id2transcripts.clear(); + id2gene.clear(); + id2TranscriptComponents.clear(); + super.activeFeatures.clear(); } @@ -340,20 +337,20 @@ private void prepareToFlushFeatures() { @Override public boolean isDone(LineIterator lineIterator) { return !lineIterator.hasNext() && - activeGenes.isEmpty() && - activeTranscriptComponents.isEmpty() && - activeTranscripts.isEmpty() && - activeUncharacterized.isEmpty() && - featuresToFlush.isEmpty(); + id2gene.isEmpty() && + id2transcripts.isEmpty() && + id2TranscriptComponents.isEmpty() && + featuresToFlush.isEmpty() && + super.activeFeatures.isEmpty(); } @Override public void close(LineIterator source) { - activeGenes.clear(); - activeTranscriptComponents.clear(); - activeTranscripts.clear(); - activeUncharacterized.clear(); + id2gene.clear(); + id2transcripts.clear(); + id2TranscriptComponents.clear(); featuresToFlush.clear(); + activeFeatures.clear(); CloserUtil.close(source); } } diff --git a/src/main/java/htsjdk/tribble/gff/GtfWriter.java b/src/main/java/htsjdk/tribble/gff/GtfWriter.java index 39ccc47c97..d19bb49688 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfWriter.java +++ b/src/main/java/htsjdk/tribble/gff/GtfWriter.java @@ -2,16 +2,11 @@ import java.io.IOException; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.nio.file.Path; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import htsjdk.samtools.util.FileExtensions; -import htsjdk.tribble.TribbleException; /** @@ -20,8 +15,7 @@ */ public class GtfWriter extends AbstractGxxWriter { - - public GtfWriter(final Path path) throws IOException { + public GtfWriter(final Path path) throws IOException { super(path, FileExtensions.GTF); } @@ -30,23 +24,21 @@ public GtfWriter(final OutputStream stream) { } + @Override protected void writeAttributes(final Map> attributes) throws IOException { if (attributes.isEmpty()) { out.write(GtfConstants.UNDEFINED_FIELD_VALUE.getBytes()); } - - writeJoinedByDelimiter(GtfConstants.ATTRIBUTE_DELIMITER, e -> writeKeyValuePair(e.getKey(), e.getValue()), attributes.entrySet()); - } - - private void writeKeyValuePair(final String key, final List values) { - try { - tryToWrite(key); - out.write(GtfConstants.VALUE_DELIMITER); - writeJoinedByDelimiter(Gff3Constants.VALUE_DELIMITER, v -> tryToWrite(escapeString(v)), values); - } catch (final IOException ex) { - throw new TribbleException("error writing out key value pair " + key + " " + values); + boolean first = true; + for(final String key: attributes.keySet()) { + for(String value : attributes.get(key)) { + if(!first) out.write(GtfConstants.ATTRIBUTE_DELIMITER); + out.write(escapeString(key).getBytes()); + out.write(GtfConstants.VALUE_DELIMITER); + out.write(escapeString(value).getBytes()); + first = false; + } } - } - -} \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/test/java/htsjdk/tribble/gff/Gff3WriterTest.java b/src/test/java/htsjdk/tribble/gff/Gff3WriterTest.java index 62178a0bb6..ec5598fa40 100644 --- a/src/test/java/htsjdk/tribble/gff/Gff3WriterTest.java +++ b/src/test/java/htsjdk/tribble/gff/Gff3WriterTest.java @@ -90,7 +90,18 @@ public void testRoundTrip(final Path path) { } private void writeToFile(final Path path, final List comments, final Set regions, final Set features) { - try (final Gff3Writer writer = new Gff3Writer(path)) { + int n=0; + System.err.println("## N="+features.size()); + try(Gff3Writer w=new Gff3Writer(System.err)) { + for (final Gff3Feature feature : features) { + w.addFeature(feature); + System.err.println("## N="+(++n)+"/"+features.size()+" "+feature.getChildren().size()); + } + } catch(Throwable err){} + + try (final AbstractGxxWriter w = AbstractGxxWriter.openWithFileExtension(path)) { + Assert.assertTrue(w instanceof Gff3Writer); + final Gff3Writer writer = Gff3Writer.class.cast(w); for (final String comment : comments) { writer.addComment(comment); } @@ -202,4 +213,4 @@ private static boolean isGZipped(final File f) { } return magic == GZIPInputStream.GZIP_MAGIC; } -} \ No newline at end of file +} diff --git a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java index c3ed73f294..a4026ab490 100644 --- a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java +++ b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java @@ -30,8 +30,8 @@ public class GtfCodecTest extends HtsjdkTest { @DataProvider(name = "basicDecodeDataProvider") Object[][] basicDecodeDataProvider() { return new Object[][]{ - {gencode47_gzipped, 2, 842}, - {gencode47_PCSK9, 1, 248} + {gencode47_gzipped, 842}, + {gencode47_PCSK9, 248} }; } @@ -39,14 +39,6 @@ private void basicDecodeTest(final Path inputGtf, GtfCodec.DecodeDepth decodeDep final GtfCodec codec = new GtfCodec(decodeDepth); Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); - try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( - DATA_DIR + "Homo_sapiens.GRCh38.97.chromosome.1.small.gff3.gz", null, new Gff3Codec(decodeDepth), false)) { - for (final Gff3Feature feature : reader.iterator()) { - - } - } - - try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( inputGtf.toAbsolutePath().toString(), null, codec, false)) { int countTotalFeatures = 0; @@ -57,19 +49,20 @@ private void basicDecodeTest(final Path inputGtf, GtfCodec.DecodeDepth decodeDep } } + @Test(dataProvider = "basicDecodeDataProvider") - public void basicDecodeDeepTest(final Path inputGtf, final int expectedCountDeep, final int _ignore) throws IOException { - basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.DEEP,expectedCountDeep); + public void basicDecodeDeepTest(final Path inputGtf, final int expectedCount) throws IOException { + basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.DEEP,expectedCount); } @Test(dataProvider = "basicDecodeDataProvider") - public void basicDecodeShallowTest(final Path inputGtf, final int _ignore, final int expectedCountShallow) throws IOException { - basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.SHALLOW,expectedCountShallow); + public void basicDecodeShallowTest(final Path inputGtf,final int expectedCount) throws IOException { + basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.SHALLOW,expectedCount); } @Test(dataProvider = "basicDecodeDataProvider") - public void codecFilterOutFieldsTest(final Path inputGtf, final int _ignore,int expectedTotalFeatures) throws IOException { + public void codecFilterOutFieldsTest(final Path inputGtf, int expectedTotalFeatures) throws IOException { final Set skip_attributes = new HashSet<>(Arrays.asList("tag","havana_gene")); final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW, S->skip_attributes.contains(S)); Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); @@ -87,6 +80,7 @@ public void codecFilterOutFieldsTest(final Path inputGtf, final int _ignore,int Assert.assertEquals(countTotalFeatures, expectedTotalFeatures); } + @Test public void FeatureContentTest() throws IOException { final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW); try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(gencode47_PCSK9.toAbsolutePath().toString(), null,codec, false)) { @@ -98,7 +92,7 @@ public void FeatureContentTest() throws IOException { Assert.assertEquals(feature.getSource(),"HAVANA"); Assert.assertEquals(feature.getType(),"UTR"); Assert.assertEquals(feature.getStart(),55039445); - Assert.assertEquals(feature.getEnd(),5503983); + Assert.assertEquals(feature.getEnd(),55039837); Assert.assertEquals(feature.getScore(),-1); Assert.assertEquals(feature.getStrand(),Strand.POSITIVE); Assert.assertEquals(feature.getPhase(),-1); @@ -120,21 +114,57 @@ public void FeatureContentTest() throws IOException { } } + @Test(dataProvider = "basicDecodeDataProvider") + private void deepTreeTest(final Path path, int expectNumber) throws IOException { + final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.DEEP); + try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(path.toAbsolutePath().toString(), null,codec, false)) { + try(CloseableIterator iter = reader.iterator()) { + Assert.assertTrue(iter.hasNext()); + while(iter.hasNext()) { + Gff3Feature f1 = iter.next(); + if(!f1.getType().equals("gene")) continue; + Assert.assertEquals(f1.getType(), "gene"); + Assert.assertTrue(f1.hasAttribute(GtfConstants.GENE_ID)); + Assert.assertTrue(f1.hasAttribute("gene_name")); + Assert.assertTrue(f1.hasChildren()); + for(Gff3Feature f2 : f1.getChildren()) { + Assert.assertEquals(f2.getType(), "transcript"); + Assert.assertTrue(f2.hasAttribute(GtfConstants.GENE_ID)); + Assert.assertTrue(f2.hasAttribute(GtfConstants.TRANSCRIPT_ID)); + Assert.assertTrue(f2.hasAttribute("gene_name")); + Assert.assertEquals(f1.getUniqueAttribute("gene_name").get(),f2.getUniqueAttribute("gene_name").get()); + Assert.assertEquals(f1.getUniqueAttribute(GtfConstants.GENE_ID).get(),f2.getUniqueAttribute(GtfConstants.GENE_ID).get()); + Assert.assertTrue(f2.hasChildren()); + + for(Gff3Feature f3 : f2.getChildren()) { + Assert.assertNotEquals(f3.getType(), "gene"); + Assert.assertNotEquals(f3.getType(), "transcript"); + Assert.assertTrue(f3.hasAttribute(GtfConstants.GENE_ID)); + Assert.assertTrue(f3.hasAttribute(GtfConstants.TRANSCRIPT_ID)); + Assert.assertEquals(f2.getUniqueAttribute(GtfConstants.GENE_ID).get(), f3.getUniqueAttribute(GtfConstants.GENE_ID).get()); + Assert.assertEquals(f2.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get(), f3.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get()); + Assert.assertFalse(f3.hasChildren()); + } + } + + } + } + } + } + + @Test public void decodeFeaturesTest() throws IOException { String s = GtfConstants.UNDEFINED_FIELD_VALUE; Map> h = GtfCodec.parseAttributes(s); Assert.assertTrue(h.isEmpty()); h = GtfCodec.parseAttributes("key1 \"ABCD\"; key2 12345 ; key3 'hello'"); - Assert.assertTrue(h.isEmpty()); for(String k: h.keySet()) { Assert.assertEquals(h.get(k).size(),1); } Assert.assertEquals(h.get("key1").get(0),"ABCD"); Assert.assertEquals(h.get("key2").get(0),"12345"); - Assert.assertEquals(h.get("key3").get(0),"hello"); - - + Assert.assertEquals(h.get("key3").get(0),"hello"); } } diff --git a/src/test/java/htsjdk/tribble/gff/GtfWriterTest.java b/src/test/java/htsjdk/tribble/gff/GtfWriterTest.java new file mode 100644 index 0000000000..4b783a546d --- /dev/null +++ b/src/test/java/htsjdk/tribble/gff/GtfWriterTest.java @@ -0,0 +1,144 @@ +package htsjdk.tribble.gff; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import htsjdk.HtsjdkTest; +import htsjdk.samtools.util.IOUtil; +import htsjdk.tribble.AbstractFeatureReader; +import htsjdk.tribble.TestUtils; +import htsjdk.tribble.TribbleException; +import htsjdk.tribble.readers.LineIterator; + +public class GtfWriterTest extends HtsjdkTest { + private final static String DATA_DIR = TestUtils.DATA_DIR + "/gff/"; + private final Path gencode47_gzipped = Paths.get(DATA_DIR + "gencode.v47.annotation.gtf.gz"); + private final Path gencode47_PCSK9 = Paths.get(DATA_DIR + "gencode.v47.PCSK9.gtf"); + private final static Path[] tmpDir = new Path[] {IOUtil.getDefaultTmpDirPath()}; + + @DataProvider(name = "roundTripDataProvider") + public Object[][] roundTripDataProvider() { + return new Object[][] { + {gencode47_gzipped}, + {gencode47_PCSK9} + }; + } + + @Test(dataProvider = "roundTripDataProvider") + public void testRoundTrip(final Path path) { + + final List comments1 = new ArrayList<>(); + final LinkedHashSet features1 = readFromFile(path, comments1); + + //write out to temp files (one gzipped, on not) + try { + int n; + IOUtil.newTempPath("gtfWriter", ".gtf", tmpDir);// REMOVE ME + final Path tempFile = Paths.get(path.toString()+".tmp.gtf");// IOUtil.newTempPath("gtfWriter", ".gtf", tmpDir); + final Path tempFileGzip = Paths.get(path.toString()+".tmp.gtf.gz");//IOUtil.newTempPath("gtfWriter", ".gtf.gz", tmpDir); + + n = writeToFile(tempFile, comments1, features1); + Assert.assertEquals(n, features1.size()); + n = writeToFile(tempFileGzip, comments1, features1); + Assert.assertEquals(n, features1.size()); + + //read temp files back in + + Assert.assertTrue(isGZipped(tempFileGzip.toFile())); + final List comments2 = new ArrayList<>(); + final LinkedHashSet features2 = readFromFile(tempFile, comments2); + Assert.assertEquals(features1.size(), features2.size()); + + + final List comments3 = new ArrayList<>(); + final LinkedHashSet features3 = readFromFile(path, comments3); + Assert.assertEquals(features1.size(), features3.size()); + + Assert.assertEquals(features1, features2); + Assert.assertEquals(features1, features3); + + Assert.assertEquals(comments1, comments2); + Assert.assertEquals(comments1, comments3); + } catch (final IOException ex) { + throw new TribbleException("Error creating temp files", ex); + } + } + + private int writeToFile(final Path path, final List comments, final Set features) { + int n = 0; + try (final AbstractGxxWriter writer =AbstractGxxWriter.openWithFileExtension(path)) { + Assert.assertTrue(writer instanceof GtfWriter); + for (final String comment : comments) { + writer.addComment(comment); + } + + for (final Gff3Feature feature : features) { + writer.addFeature(feature); + n++; + } + return n; + } catch (final IOException ex) { + throw new TribbleException("Error writing to file " + path, ex); + } + } + + private LinkedHashSet readFromFile(final Path path, List commentsStore) { + final GtfCodec codec = new GtfCodec(); + final LinkedHashSet features = new LinkedHashSet<>(); + try (final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( + path.toAbsolutePath().toString(), null, codec, false)) { + for (final Gff3Feature feature : reader.iterator()) { + features.add(feature); + } + + commentsStore.addAll(codec.getCommentTexts()); + } catch (final IOException ex) { + throw new TribbleException("Error reading gtf file " + path); + } + System.err.println("###ICIB "+features.size()); + return features; + } + + @DataProvider(name = "encodeStringDataProvider") + public Object[][] encodeStringDataProvider() { + return new Object[][] { + {"%", "%25"}, + {";", "%3B"}, + {"=", "%3D"}, + {"&", "%26"}, + {",", "%2C"}, + {" ", " "}, + {"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ", "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM "} //these should remain unchanged + }; + } + + @Test(dataProvider = "encodeStringDataProvider") + public void testEncodeString(final String decoded, final String expectedEncoded) { + final String encoded = GtfWriter.encodeString(decoded); + Assert.assertEquals(encoded, expectedEncoded); + } + + private static boolean isGZipped(final File f) { + int magic = 0; + try { + RandomAccessFile raf = new RandomAccessFile(f, "r"); + magic = raf.read() & 0xff | ((raf.read() << 8) & 0xff00); + raf.close(); + } catch (Throwable e) { + e.printStackTrace(System.err); + } + return magic == GZIPInputStream.GZIP_MAGIC; + } +} From 6eb06075e5ea57a6d1fbf0c3bf6e95d1cbead065 Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Tue, 15 Apr 2025 11:34:19 +0200 Subject: [PATCH 6/7] Support for GTF files: Codec and Writer --- .../htsjdk/tribble/gff/AbstractGxxCodec.java | 104 ++-- .../tribble/gff/AbstractGxxConstants.java | 17 +- .../htsjdk/tribble/gff/AbstractGxxWriter.java | 77 +-- .../java/htsjdk/tribble/gff/Gff3BaseData.java | 84 +-- .../java/htsjdk/tribble/gff/Gff3Codec.java | 175 ++++--- .../htsjdk/tribble/gff/Gff3Constants.java | 12 +- .../java/htsjdk/tribble/gff/Gff3Feature.java | 82 ++- .../htsjdk/tribble/gff/Gff3FeatureImpl.java | 157 +++--- .../java/htsjdk/tribble/gff/Gff3Writer.java | 31 +- .../java/htsjdk/tribble/gff/GtfCodec.java | 479 ++++++++++-------- .../java/htsjdk/tribble/gff/GtfConstants.java | 12 +- .../java/htsjdk/tribble/gff/GtfWriter.java | 33 +- .../java/htsjdk/tribble/gff/GtfCodecTest.java | 221 ++++---- .../htsjdk/tribble/gff/GtfWriterTest.java | 45 +- 14 files changed, 880 insertions(+), 649 deletions(-) diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java index 104dabde17..0e65316ed3 100644 --- a/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxCodec.java @@ -30,7 +30,6 @@ /** * Abstract Base Codec for parsing Gff3 files or GTF codec */ - public abstract class AbstractGxxCodec extends AbstractFeatureCodec { protected static final int NUM_FIELDS = 9; @@ -57,72 +56,93 @@ public abstract class AbstractGxxCodec extends AbstractFeatureCodec filterOutAttribute; - + /** * @param decodeDepth a value from DecodeDepth - * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column + * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column */ - protected AbstractGxxCodec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { + protected AbstractGxxCodec(final DecodeDepth decodeDepth, + final Predicate filterOutAttribute) { super(Gff3Feature.class); this.decodeDepth = decodeDepth; this.filterOutAttribute = filterOutAttribute; - } - + } + public enum DecodeDepth { - DEEP , - SHALLOW + DEEP, SHALLOW } - + @Override public final Gff3Feature decode(final LineIterator lineIterator) throws IOException { return decode(lineIterator, decodeDepth); } - protected abstract Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) throws IOException; + protected abstract Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) + throws IOException; + - /** * Parse attributes field for gff3 feature + * * @param attributesString attributes field string from line in gff3 file * @return map of keys to values for attributes of this feature * @throws UnsupportedEncodingException */ - protected abstract Map> parseAttributesColumn(final String attributesString) throws UnsupportedEncodingException; + protected abstract Map> parseAttributesColumn( + final String attributesString) throws UnsupportedEncodingException; - protected final Gff3BaseData parseLine(final String line, final int currentLine, final Predicate filterOutAttribute) { - final List splitLine = ParsingUtils.split(line, AbstractGxxConstants.FIELD_DELIMITER); + protected final Gff3BaseData parseLine(final String line, final int currentLine, + final Predicate filterOutAttribute) { + final List splitLine = + ParsingUtils.split(line, AbstractGxxConstants.FIELD_DELIMITER); if (splitLine.size() != NUM_FIELDS) { - throw new TribbleException("Found an invalid number of columns in the given Gff3/GTF file at line + " + currentLine + " - Given: " + splitLine.size() + " Expected: " + NUM_FIELDS + " : " + line); + throw new TribbleException( + "Found an invalid number of columns in the given Gff3/GTF file at line + " + + currentLine + " - Given: " + splitLine.size() + " Expected: " + + NUM_FIELDS + " : " + line); } try { final String contig = URLDecoder.decode(splitLine.get(CHROMOSOME_NAME_INDEX), "UTF-8"); - final String source = URLDecoder.decode(splitLine.get(ANNOTATION_SOURCE_INDEX), "UTF-8"); + final String source = + URLDecoder.decode(splitLine.get(ANNOTATION_SOURCE_INDEX), "UTF-8"); final String type = URLDecoder.decode(splitLine.get(FEATURE_TYPE_INDEX), "UTF-8"); final int start = Integer.parseInt(splitLine.get(START_LOCATION_INDEX)); final int end = Integer.parseInt(splitLine.get(END_LOCATION_INDEX)); - final double score = splitLine.get(SCORE_INDEX).equals(AbstractGxxConstants.UNDEFINED_FIELD_VALUE) ? -1 : Double.parseDouble(splitLine.get(SCORE_INDEX)); - final int phase = splitLine.get(GENOMIC_PHASE_INDEX).equals(AbstractGxxConstants.UNDEFINED_FIELD_VALUE) ? -1 : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); + final double score = + splitLine.get(SCORE_INDEX).equals(AbstractGxxConstants.UNDEFINED_FIELD_VALUE) + ? -1 + : Double.parseDouble(splitLine.get(SCORE_INDEX)); + final int phase = splitLine.get(GENOMIC_PHASE_INDEX) + .equals(AbstractGxxConstants.UNDEFINED_FIELD_VALUE) ? -1 + : Integer.parseInt(splitLine.get(GENOMIC_PHASE_INDEX)); final Strand strand = Strand.decode(splitLine.get(GENOMIC_STRAND_INDEX)); - final Map> attributes = parseAttributesColumn(splitLine.get(EXTRA_FIELDS_INDEX)); + final Map> attributes = + parseAttributesColumn(splitLine.get(EXTRA_FIELDS_INDEX)); /* remove attibutes matching 'filterOutAttribute' */ attributes.keySet().removeIf(filterOutAttribute); - return new Gff3BaseData(contig, source, type, start, end, score, strand, phase, attributes); - } catch (final NumberFormatException ex ) { - throw new TribbleException("Cannot read integer value for start/end position from line " + currentLine + ". Line is: " + line, ex); + return new Gff3BaseData(contig, source, type, start, end, score, strand, phase, + attributes); + } catch (final NumberFormatException ex) { + throw new TribbleException("Cannot read integer value for start/end position from line " + + currentLine + ". Line is: " + line, ex); } catch (final IOException ex) { - throw new TribbleException("Cannot decode feature info from line " + currentLine + ". Line is: " + line, ex); + throw new TribbleException( + "Cannot decode feature info from line " + currentLine + ". Line is: " + line, + ex); } } /** - * Gets map from line number to comment found on that line. The text of the comment EXCLUDES the leading # which indicates a comment line. + * Gets map from line number to comment found on that line. The text of the comment EXCLUDES the + * leading # which indicates a comment line. + * * @return Map from line number to comment found on line */ public final Map getCommentsWithLineNumbers() { @@ -130,7 +150,8 @@ public final Map getCommentsWithLineNumbers() { } /** - * Gets list of comments parsed by the codec. Excludes leading # which indicates a comment line. + * Gets list of comments parsed by the codec. Excludes leading # which indicates a comment line. + * * @return */ public final List getCommentTexts() { @@ -149,22 +170,22 @@ protected boolean canDecodeFirstLine(final String line) { // make sure line conforms to gtf spec final List fields = ParsingUtils.split(line, AbstractGxxConstants.FIELD_DELIMITER); - if(fields.size() != NUM_FIELDS) return false;; + if (fields.size() != NUM_FIELDS) + return false;; - // check that start and end fields are integers - try { - /* final int start = */ Integer.parseInt(fields.get(3)); - /* final int end = */ Integer.parseInt(fields.get(4)); - } catch (NumberFormatException | NullPointerException nfe) { - return false; - } + // check that start and end fields are integers + try { + /* final int start = */ Integer.parseInt(fields.get(3)); + /* final int end = */ Integer.parseInt(fields.get(4)); + } catch (NumberFormatException | NullPointerException nfe) { + return false; + } - // check for strand - final String strand = fields.get(GENOMIC_STRAND_INDEX); - return strand.equals(Strand.POSITIVE.toString()) || - strand.equals(Strand.NEGATIVE.toString()) || - strand.equals(Strand.NONE.toString()) || - strand.equals("?"); + // check for strand + final String strand = fields.get(GENOMIC_STRAND_INDEX); + return strand.equals(Strand.POSITIVE.toString()) + || strand.equals(Strand.NEGATIVE.toString()) + || strand.equals(Strand.NONE.toString()) || strand.equals("?"); } static String extractSingleAttribute(final List values) { @@ -182,7 +203,7 @@ static String extractSingleAttribute(final List values) { public final FeatureCodecHeader readHeader(LineIterator lineIterator) { List header = new ArrayList<>(); - while(lineIterator.hasNext()) { + while (lineIterator.hasNext()) { String line = lineIterator.peek(); if (line.startsWith(Gff3Constants.COMMENT_START)) { header.add(line); @@ -201,7 +222,8 @@ public final LineIterator makeSourceFromStream(final InputStream bufferedInputSt } @Override - public final LocationAware makeIndexableSourceFromStream(final InputStream bufferedInputStream) { + public final LocationAware makeIndexableSourceFromStream( + final InputStream bufferedInputStream) { return new AsciiLineReaderIterator(AsciiLineReader.from(bufferedInputStream)); } diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java index 15032a7b8e..c4938e05e9 100644 --- a/src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxConstants.java @@ -1,10 +1,13 @@ package htsjdk.tribble.gff; +/** + * Common constants shared by GtfCodec and Gff3Codec + */ public class AbstractGxxConstants { - public static final char ATTRIBUTE_DELIMITER = ';'; - public static final char FIELD_DELIMITER = '\t'; - public static final String COMMENT_START = "#"; - public static final String DIRECTIVE_START = "##"; - public static final String UNDEFINED_FIELD_VALUE = "."; - public final static char END_OF_LINE_CHARACTER = '\n'; -} + public static final char ATTRIBUTE_DELIMITER = ';'; + public static final char FIELD_DELIMITER = '\t'; + public static final String COMMENT_START = "#"; + public static final String DIRECTIVE_START = "##"; + public static final String UNDEFINED_FIELD_VALUE = "."; + public final static char END_OF_LINE_CHARACTER = '\n'; +} \ No newline at end of file diff --git a/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java b/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java index 4964870405..7e565de783 100644 --- a/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java +++ b/src/main/java/htsjdk/tribble/gff/AbstractGxxWriter.java @@ -22,19 +22,25 @@ /** - * A class to write out gff3/gtf files. Features are added using {@link #addFeature(Gff3Feature)}, directives using {@link #addDirective(Gff3Codec.Gff3Directive)}, - * and comments using {@link #addComment(String)}. Note that the version 3 directive is automatically added at creation, so should not be added separately. + * A class to write out gff3/gtf files. Features are added using {@link #addFeature(Gff3Feature)}, + * directives using {@link #addDirective(Gff3Codec.Gff3Directive)}, and comments using + * {@link #addComment(String)}. Note that the version 3 directive is automatically added at + * creation, so should not be added separately. */ public abstract class AbstractGxxWriter implements Closeable { protected final OutputStream out; - protected AbstractGxxWriter(final Path path, final Set fileExtensions) throws IOException { + protected AbstractGxxWriter(final Path path, final Set fileExtensions) + throws IOException { if (fileExtensions.stream().noneMatch(e -> path.toString().endsWith(e))) { - throw new TribbleException("File " + path + " does not have extension consistent with gff3/gtf"); + throw new TribbleException( + "File " + path + " does not have extension consistent with gff3/gtf"); } - final OutputStream outputStream = IOUtil.hasGzipFileExtension(path)? new BlockCompressedOutputStream(path.toFile()) : Files.newOutputStream(path); + final OutputStream outputStream = + IOUtil.hasGzipFileExtension(path) ? new BlockCompressedOutputStream(path.toFile()) + : Files.newOutputStream(path); out = new BufferedOutputStream(outputStream); } @@ -56,22 +62,22 @@ protected final void tryToWrite(final String string) { } protected final void writeFirstEightFields(final Gff3Feature feature) throws IOException { - writeJoinedByDelimiter(AbstractGxxConstants.FIELD_DELIMITER, this::tryToWrite, Arrays.asList( - escapeString(feature.getContig()), - escapeString(feature.getSource()), - escapeString(feature.getType()), - Integer.toString(feature.getStart()), - Integer.toString(feature.getEnd()), - feature.getScore() < 0 ? AbstractGxxConstants.UNDEFINED_FIELD_VALUE : Double.toString(feature.getScore()), - feature.getStrand().toString(), - feature.getPhase() < 0 ? AbstractGxxConstants.UNDEFINED_FIELD_VALUE : Integer.toString(feature.getPhase()) - ) - ); + writeJoinedByDelimiter(AbstractGxxConstants.FIELD_DELIMITER, this::tryToWrite, + Arrays.asList(escapeString(feature.getContig()), escapeString(feature.getSource()), + escapeString(feature.getType()), Integer.toString(feature.getStart()), + Integer.toString(feature.getEnd()), + feature.getScore() < 0 ? AbstractGxxConstants.UNDEFINED_FIELD_VALUE + : Double.toString(feature.getScore()), + feature.getStrand().toString(), + feature.getPhase() < 0 ? AbstractGxxConstants.UNDEFINED_FIELD_VALUE + : Integer.toString(feature.getPhase()))); } - protected abstract void writeAttributes(final Map> attributes) throws IOException; + protected abstract void writeAttributes(final Map> attributes) + throws IOException; - protected final void writeJoinedByDelimiter(final char delimiter, final Consumer consumer, final Collection fields) throws IOException { + protected final void writeJoinedByDelimiter(final char delimiter, + final Consumer consumer, final Collection fields) throws IOException { boolean isNotFirstField = false; for (final T field : fields) { if (isNotFirstField) { @@ -86,19 +92,20 @@ protected final void writeJoinedByDelimiter(final char delimiter, final Cons /*** * add a feature + * * @param feature the feature to be added * @throws IOException */ public void addFeature(final Gff3Feature feature) throws IOException { - writeFirstEightFields(feature); + writeFirstEightFields(feature); out.write(AbstractGxxConstants.FIELD_DELIMITER); writeAttributes(feature.getAttributes()); out.write(AbstractGxxConstants.END_OF_LINE_CHARACTER); } /*** - * escape a String. - * Default behavior is to call {@link #encodeString(String)} + * escape a String. Default behavior is to call {@link #encodeString(String)} + * * @param s the string to be escaped * @return the escaped string */ @@ -108,8 +115,9 @@ protected String escapeString(final String s) { static String encodeString(final String s) { try { - //URLEncoder.encode is hardcoded to change all spaces to +, but we want spaces left unchanged so have to do this - //+ is escaped to %2B, so no loss of information + // URLEncoder.encode is hardcoded to change all spaces to +, but we want spaces left + // unchanged so have to do this + // + is escaped to %2B, so no loss of information return URLEncoder.encode(s, "UTF-8").replace("+", " "); } catch (final UnsupportedEncodingException ex) { throw new TribbleException("Encoding failure", ex); @@ -118,6 +126,7 @@ static String encodeString(final String s) { /** * Add comment line + * * @param comment the comment line (not including leading #) * @throws IOException */ @@ -130,19 +139,17 @@ public final void addComment(final String comment) throws IOException { public final void close() throws IOException { out.close(); } - + /** opens a writer as a Gff3Writer or as a GtfWriter using the path suffix */ public static AbstractGxxWriter openWithFileExtension(final Path path) throws IOException { if (FileExtensions.GFF3.stream().anyMatch(e -> path.toString().endsWith(e))) { - return new Gff3Writer(path); - } - else if (FileExtensions.GTF.stream().anyMatch(e -> path.toString().endsWith(e))) { - return new GtfWriter(path); - } - else - { - throw new IllegalArgumentException(path.toString()+" doesn't have a gtf or a gff3 suffix"); - } - } - + return new Gff3Writer(path); + } else if (FileExtensions.GTF.stream().anyMatch(e -> path.toString().endsWith(e))) { + return new GtfWriter(path); + } else { + throw new IllegalArgumentException( + path.toString() + " doesn't have a gtf or a gff3 suffix"); + } + } + } diff --git a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java index 6b31e5744e..ffe02d1594 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java @@ -26,8 +26,8 @@ public class Gff3BaseData implements Locatable { private final int hashCode; public Gff3BaseData(final String contig, final String source, final String type, - final int start, final int end, final Double score, final Strand strand, final int phase, - final Map> attributes) { + final int start, final int end, final Double score, final Strand strand, + final int phase, final Map> attributes) { this.contig = contig; this.source = source; this.type = type; @@ -38,16 +38,20 @@ public Gff3BaseData(final String contig, final String source, final String type, this.strand = strand; this.attributes = copyAttributesSafely(attributes); this.id = Gff3Codec.extractSingleAttribute(attributes.get(Gff3Constants.ID_ATTRIBUTE_KEY)); - this.name = Gff3Codec.extractSingleAttribute(attributes.get(Gff3Constants.NAME_ATTRIBUTE_KEY)); - this.aliases = attributes.getOrDefault(Gff3Constants.ALIAS_ATTRIBUTE_KEY, Collections.emptyList()); + this.name = + Gff3Codec.extractSingleAttribute(attributes.get(Gff3Constants.NAME_ATTRIBUTE_KEY)); + this.aliases = + attributes.getOrDefault(Gff3Constants.ALIAS_ATTRIBUTE_KEY, Collections.emptyList()); this.hashCode = computeHashCode(); } - private static Map> copyAttributesSafely(final Map> attributes) { + private static Map> copyAttributesSafely( + final Map> attributes) { final Map> modifiableDeepMap = new LinkedHashMap<>(); for (final Map.Entry> entry : attributes.entrySet()) { - final List unmodifiableDeepList = Collections.unmodifiableList(new ArrayList<>(entry.getValue())); + final List unmodifiableDeepList = + Collections.unmodifiableList(new ArrayList<>(entry.getValue())); modifiableDeepMap.put(entry.getKey(), unmodifiableDeepList); } @@ -59,20 +63,19 @@ public boolean equals(Object other) { if (other == this) { return true; } - if(!other.getClass().equals(Gff3BaseData.class)) { + if (!other.getClass().equals(Gff3BaseData.class)) { return false; } final Gff3BaseData otherBaseData = (Gff3BaseData) other; - boolean ret = otherBaseData.getContig().equals(getContig()) && - otherBaseData.getSource().equals(getSource()) && - otherBaseData.getType().equals(getType()) && - otherBaseData.getStart() == getStart() && - otherBaseData.getEnd() == getEnd() && - ((Double)otherBaseData.getScore()).equals(score) && - otherBaseData.getPhase() == getPhase() && - otherBaseData.getStrand().equals(getStrand()) && - otherBaseData.getAttributes().equals(getAttributes()); + boolean ret = otherBaseData.getContig().equals(getContig()) + && otherBaseData.getSource().equals(getSource()) + && otherBaseData.getType().equals(getType()) + && otherBaseData.getStart() == getStart() && otherBaseData.getEnd() == getEnd() + && ((Double) otherBaseData.getScore()).equals(score) + && otherBaseData.getPhase() == getPhase() + && otherBaseData.getStrand().equals(getStrand()) + && otherBaseData.getAttributes().equals(getAttributes()); if (getId() == null) { ret = ret && otherBaseData.getId() == null; } else { @@ -82,7 +85,8 @@ public boolean equals(Object other) { if (getName() == null) { ret = ret && otherBaseData.getName() == null; } else { - ret = ret && otherBaseData.getName() != null && otherBaseData.getName().equals(getName()); + ret = ret && otherBaseData.getName() != null + && otherBaseData.getName().equals(getName()); } ret = ret && otherBaseData.getAliases().equals(getAliases()); @@ -157,8 +161,9 @@ public Map> getAttributes() { return attributes; } - /** - * get the values as List for the key, or an empty list if this key is not present + /** + * get the values as List for the key, or an empty list if this key is not + * present * * @param key key whose presence in this map is to be tested * @return the values as List, or an empty list if this key is not present @@ -176,26 +181,33 @@ public List getAttribute(final String key) { public boolean hasAttribute(final String key) { return attributes.containsKey(key); } - + /** - * Most attributes in a GFF file are present just one time in a line, e.g. : gene_biotype, gene_name, etc ... - * This function returns an Optional.empty if the key is not present, - * an Optional.of(value) if there is only one value associated to the key, - * or it throws an IllegalArgumentException if there is more than one value. + * Most attributes in a GFF file are present just one time in a line, e.g. : + * gene_biotype, gene_name, etc ... This function returns an + * Optional.empty if the key is not present, an Optional.of(value) if + * there is only one value associated to the key, or it throws an + * IllegalArgumentException if there is more than one value. * * @param key key whose presence in the attributes is to be tested - * @return Optional<String> if this map contains zero or one attribute for the specified key + * @return Optional<String> if this map contains zero or one attribute for the + * specified key * @throws IllegalArgumentException if there is more than one value */ public Optional getUniqueAttribute(final String key) { final List atts = getAttribute(key); - switch(atts.size()) { - case 0 : return Optional.empty(); - case 1 : return Optional.of(atts.get(0)); - default : throw new IllegalArgumentException("getUniqueAttribute cannot be called with key="+key+" because it contains more than one value " + String.join(", ", atts)); + switch (atts.size()) { + case 0: + return Optional.empty(); + case 1: + return Optional.of(atts.get(0)); + default: + throw new IllegalArgumentException("getUniqueAttribute cannot be called with key=" + + key + " because it contains more than one value " + + String.join(", ", atts)); } } - + public String getId() { return id; } @@ -208,10 +220,10 @@ public List getAliases() { return aliases; } - @Override - public String toString() { - return "Gff3BaseData [contig=" + contig + ", source=" + source + ", type=" + type + ", start=" + start - + ", end=" + end + ", score=" + score + ", strand=" + strand + ", phase=" + phase + ", attributes=" - + attributes + "]"; - } + @Override + public String toString() { + return "Gff3BaseData [contig=" + contig + ", source=" + source + ", type=" + type + + ", start=" + start + ", end=" + end + ", score=" + score + ", strand=" + strand + + ", phase=" + phase + ", attributes=" + attributes + "]"; + } } diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java index 049b8c2d3c..5a211bcf81 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Codec.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Codec.java @@ -30,12 +30,18 @@ import htsjdk.tribble.util.ParsingUtils; /** - * Codec for parsing Gff3 files, as defined in https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md - * Note that while spec states that all feature types must be defined in sequence ontology, this implementation makes no check on feature types, and allows any string as feature type + * Codec for parsing Gff3 files, as defined in + * https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md + * Note that while spec states that all feature types must be defined in sequence ontology, this + * implementation makes no check on feature types, and allows any string as feature type * - * Each feature line in the Gff3 file will be emitted as a separate feature. Features linked together through the "Parent" attribute will be linked through {@link Gff3Feature#getParents()}, {@link Gff3Feature#getChildren()}, - * {@link Gff3Feature#getAncestors()}, {@link Gff3Feature#getDescendents()}, amd {@link Gff3Feature#flatten()}. This linking is not guaranteed to be comprehensive when the file is read for only features overlapping a particular - * region, using a tribble index. In this case, a particular feature will only be linked to the subgroup of features it is linked to in the input file which overlap the given region. + * Each feature line in the Gff3 file will be emitted as a separate feature. Features linked + * together through the "Parent" attribute will be linked through {@link Gff3Feature#getParents()}, + * {@link Gff3Feature#getChildren()}, {@link Gff3Feature#getAncestors()}, + * {@link Gff3Feature#getDescendents()}, amd {@link Gff3Feature#flatten()}. This linking is not + * guaranteed to be comprehensive when the file is read for only features overlapping a particular + * region, using a tribble index. In this case, a particular feature will only be linked to the + * subgroup of features it is linked to in the input file which overlap the given region. */ public class Gff3Codec extends AbstractGxxCodec { @@ -51,7 +57,7 @@ public class Gff3Codec extends AbstractGxxCodec { private final static Log logger = Log.getInstance(Gff3Codec.class); private boolean reachedFasta = false; - + public Gff3Codec() { this(DecodeDepth.DEEP); } @@ -62,28 +68,32 @@ public Gff3Codec(final DecodeDepth decodeDepth) { /** * @param decodeDepth a value from DecodeDepth - * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column + * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column */ public Gff3Codec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { - super(decodeDepth,filterOutAttribute); + super(decodeDepth, filterOutAttribute); /* check required keys are always kept */ - for (final String key : new String[] {Gff3Constants.PARENT_ATTRIBUTE_KEY, Gff3Constants.ID_ATTRIBUTE_KEY, Gff3Constants.NAME_ATTRIBUTE_KEY}) { + for (final String key : new String[] {Gff3Constants.PARENT_ATTRIBUTE_KEY, + Gff3Constants.ID_ATTRIBUTE_KEY, Gff3Constants.NAME_ATTRIBUTE_KEY}) { if (filterOutAttribute.test(key)) { throw new IllegalArgumentException("Predicate should always accept " + key); - } + } } } @Override - protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) throws IOException { + protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth depth) + throws IOException { currentLine++; /* - Basic strategy: Load features into deque, create maps from a features ID to it, and from a features parents' IDs to it. For each feature, link to parents using these maps. - When reaching flush directive, fasta, or end of file, prepare to flush features by moving all active features to deque of features to flush, and clearing - list of active features and both maps. Always poll featuresToFlush to return any completed top level features. + * Basic strategy: Load features into deque, create maps from a features ID to it, and from + * a features parents' IDs to it. For each feature, link to parents using these maps. When + * reaching flush directive, fasta, or end of file, prepare to flush features by moving all + * active features to deque of features to flush, and clearing list of active features and + * both maps. Always poll featuresToFlush to return any completed top level features. */ if (!lineIterator.hasNext()) { - //no more lines, flush whatever is active + // no more lines, flush whatever is active prepareToFlushFeatures(); return featuresToFlush.poll(); } @@ -91,19 +101,21 @@ protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth final String line = lineIterator.next(); if (reachedFasta) { - //previously reached fasta, flush whatever is active + // previously reached fasta, flush whatever is active prepareToFlushFeatures(); return featuresToFlush.poll(); } if (line.startsWith(ARTEMIS_FASTA_MARKER)) { - //backwards compatability with Artemis is built into gff3 spec + // backwards compatability with Artemis is built into gff3 spec processDirective(Gff3Directive.FASTA_DIRECTIVE, null); return featuresToFlush.poll(); } - if (line.startsWith(Gff3Constants.COMMENT_START) && !line.startsWith(Gff3Constants.DIRECTIVE_START)) { - commentsWithLineNumbers.put(currentLine, line.substring(Gff3Constants.COMMENT_START.length())); + if (line.startsWith(Gff3Constants.COMMENT_START) + && !line.startsWith(Gff3Constants.DIRECTIVE_START)) { + commentsWithLineNumbers.put(currentLine, + line.substring(Gff3Constants.COMMENT_START.length())); return featuresToFlush.poll(); } @@ -113,11 +125,13 @@ protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth } - final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, super.filterOutAttribute)); + final Gff3FeatureImpl thisFeature = + new Gff3FeatureImpl(parseLine(line, currentLine, super.filterOutAttribute)); activeFeatures.add(thisFeature); if (depth == DecodeDepth.DEEP) { - //link to parents/children/co-features - final List parentIDs = thisFeature.getAttribute(Gff3Constants.PARENT_ATTRIBUTE_KEY); + // link to parents/children/co-features + final List parentIDs = + thisFeature.getAttribute(Gff3Constants.PARENT_ATTRIBUTE_KEY); final String id = thisFeature.getID(); for (final String parentID : parentIDs) { @@ -131,7 +145,8 @@ protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth if (activeParentIDs.containsKey(parentID)) { activeParentIDs.get(parentID).add(thisFeature); } else { - activeParentIDs.put(parentID, new HashSet<>(Collections.singleton(thisFeature))); + activeParentIDs.put(parentID, + new HashSet<>(Collections.singleton(thisFeature))); } } @@ -142,7 +157,8 @@ protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth } activeFeaturesWithIDs.get(id).add(thisFeature); } else { - activeFeaturesWithIDs.put(id, new HashSet<>(Collections.singleton(thisFeature))); + activeFeaturesWithIDs.put(id, + new HashSet<>(Collections.singleton(thisFeature))); } } @@ -155,41 +171,48 @@ protected Gff3Feature decode(final LineIterator lineIterator, final DecodeDepth validateFeature(thisFeature); if (depth == DecodeDepth.SHALLOW) { - //flush all features immediatly + // flush all features immediatly prepareToFlushFeatures(); } return featuresToFlush.poll(); } - @Override - protected Map> parseAttributesColumn(String attributesString) - throws UnsupportedEncodingException { - return parseAttributes(attributesString); - } + @Override + protected Map> parseAttributesColumn(String attributesString) + throws UnsupportedEncodingException { + return parseAttributes(attributesString); + } + /** * Parse attributes field for gff3 feature + * * @param attributesString attributes field string from line in gff3 file * @return map of keys to values for attributes of this feature * @throws UnsupportedEncodingException */ - static private Map> parseAttributes(final String attributesString) throws UnsupportedEncodingException { + static private Map> parseAttributes(final String attributesString) + throws UnsupportedEncodingException { if (attributesString.equals(Gff3Constants.UNDEFINED_FIELD_VALUE)) { return Collections.emptyMap(); } final Map> attributes = new LinkedHashMap<>(); - final List splitLine = ParsingUtils.split(attributesString,Gff3Constants.ATTRIBUTE_DELIMITER); - for(String attribute : splitLine) { - final List key_value = ParsingUtils.split(attribute,Gff3Constants.KEY_VALUE_SEPARATOR); + final List splitLine = + ParsingUtils.split(attributesString, Gff3Constants.ATTRIBUTE_DELIMITER); + for (String attribute : splitLine) { + final List key_value = + ParsingUtils.split(attribute, Gff3Constants.KEY_VALUE_SEPARATOR); if (key_value.size() != 2) { throw new TribbleException("Attribute string " + attributesString + " is invalid"); } - attributes.put(URLDecoder.decode(key_value.get(0).trim(), "UTF-8"), decodeAttributeValue(key_value.get(1).trim())); + attributes.put(URLDecoder.decode(key_value.get(0).trim(), "UTF-8"), + decodeAttributeValue(key_value.get(1).trim())); } return attributes; } /** * Get list of sequence regions parsed by the codec. + * * @return list of sequence regions */ public List getSequenceRegions() { @@ -198,21 +221,26 @@ public List getSequenceRegions() { /** - * If sequence region of feature's contig has been specified with sequence region directive, validates that - * feature's coordinates are within the specified sequence region. TribbleException is thrown if invalid. + * If sequence region of feature's contig has been specified with sequence region directive, + * validates that feature's coordinates are within the specified sequence region. + * TribbleException is thrown if invalid. + * * @param feature */ private void validateFeature(final Gff3Feature feature) { if (sequenceRegionMap.containsKey(feature.getContig())) { final SequenceRegion region = sequenceRegionMap.get(feature.getContig()); if (feature.getStart() == region.getStart() && feature.getEnd() == region.getEnd()) { - //landmark feature - final boolean isCircular = Boolean.parseBoolean(extractSingleAttribute(feature.getAttribute(IS_CIRCULAR_ATTRIBUTE_KEY))); + // landmark feature + final boolean isCircular = Boolean.parseBoolean( + extractSingleAttribute(feature.getAttribute(IS_CIRCULAR_ATTRIBUTE_KEY))); region.setCircular(isCircular); } - if (region.isCircular()? !region.overlaps(feature) : !region.contains(feature)) { - throw new TribbleException("feature at " + feature.getContig() + ":" + feature.getStart() + "-" + feature.getEnd() + - " not contained in specified sequence region (" + region.getContig() + ":" + region.getStart() + "-" + region.getEnd()); + if (region.isCircular() ? !region.overlaps(feature) : !region.contains(feature)) { + throw new TribbleException("feature at " + feature.getContig() + ":" + + feature.getStart() + "-" + feature.getEnd() + + " not contained in specified sequence region (" + region.getContig() + ":" + + region.getStart() + "-" + region.getEnd()); } } } @@ -223,14 +251,16 @@ public boolean canDecode(final String inputFilePath) { try { // Simple file and name checks to start with: Path p = IOUtil.getPath(inputFilePath); - if(!FileExtensions.GFF3.stream().anyMatch(fe -> p.toString().endsWith(fe))) { - return false; - } + if (!FileExtensions.GFF3.stream().anyMatch(fe -> p.toString().endsWith(fe))) { + return false; + } // Crack open the file and look at the top of it: - final InputStream inputStream = IOUtil.hasGzipFileExtension(p)? new GZIPInputStream(Files.newInputStream(p)) : Files.newInputStream(p); + final InputStream inputStream = + IOUtil.hasGzipFileExtension(p) ? new GZIPInputStream(Files.newInputStream(p)) + : Files.newInputStream(p); - try ( BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) ) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { String line = br.readLine(); @@ -240,26 +270,25 @@ public boolean canDecode(final String inputFilePath) { } while (line.startsWith(Gff3Constants.COMMENT_START)) { line = br.readLine(); - if ( line == null ) { + if (line == null) { return false; } } - return canDecodeFirstLine(line); + return canDecodeFirstLine(line); } - } - catch (final FileNotFoundException ex) { + } catch (final FileNotFoundException ex) { logger.error(inputFilePath + " not found."); return false; - } - catch (final IOException ex) { + } catch (final IOException ex) { return false; } } static List decodeAttributeValue(final String attributeValue) { - //split on VALUE_DELIMITER, then decode - final List splitValues = ParsingUtils.split(attributeValue, Gff3Constants.VALUE_DELIMITER); + // split on VALUE_DELIMITER, then decode + final List splitValues = + ParsingUtils.split(attributeValue, Gff3Constants.VALUE_DELIMITER); final List decodedValues = new ArrayList<>(); for (final String encodedValue : splitValues) { @@ -287,6 +316,7 @@ static String extractSingleAttribute(final List values) { /** * Parse a directive line from a gff3 file + * * @param directiveLine * @throws IOException */ @@ -302,6 +332,7 @@ private void parseDirective(final String directiveLine) throws IOException { /** * Process a gff3 directive + * * @param directive the gff3 directive, indicated by a specific directive line * @param decodedResult the decoding of the directive line by the given directive */ @@ -313,7 +344,8 @@ private void processDirective(final Gff3Directive directive, final Object decode case SEQUENCE_REGION_DIRECTIVE: final SequenceRegion newRegion = (SequenceRegion) decodedResult; if (sequenceRegionMap.containsKey(newRegion.getContig())) { - throw new TribbleException("directive for sequence-region " + newRegion.getContig() + " included more than once."); + throw new TribbleException("directive for sequence-region " + + newRegion.getContig() + " included more than once."); } sequenceRegionMap.put(newRegion.getContig(), newRegion); break; @@ -327,13 +359,14 @@ private void processDirective(final Gff3Directive directive, final Object decode break; default: - throw new IllegalArgumentException( "Directive " + directive + " has been added to Gff3Directive, but is not being handled by Gff3Codec::processDirective. This is a BUG."); + throw new IllegalArgumentException("Directive " + directive + + " has been added to Gff3Directive, but is not being handled by Gff3Codec::processDirective. This is a BUG."); } } /** - * move active top level features to featuresToFlush. clear active features. + * move active top level features to featuresToFlush. clear active features. */ private void prepareToFlushFeatures() { featuresToFlush.addAll(activeFeatures); @@ -342,7 +375,7 @@ private void prepareToFlushFeatures() { activeParentIDs.clear(); } - + @Override public boolean isDone(final LineIterator lineIterator) { return !lineIterator.hasNext() && activeFeatures.isEmpty() && featuresToFlush.isEmpty(); @@ -350,7 +383,7 @@ public boolean isDone(final LineIterator lineIterator) { @Override public void close(final LineIterator lineIterator) { - //cleanup resources + // cleanup resources featuresToFlush.clear(); activeFeaturesWithIDs.clear(); activeFeatures.clear(); @@ -359,13 +392,14 @@ public void close(final LineIterator lineIterator) { } /** - * Enum for parsing directive lines. If information in directive line needs to be parsed beyond specifying directive type, decode method should be overriden + * Enum for parsing directive lines. If information in directive line needs to be parsed beyond + * specifying directive type, decode method should be overriden */ public enum Gff3Directive { VERSION3_DIRECTIVE("##gff-version\\s+3(?:\\.\\d*)*$") { @Override - protected Object decode(final String line) throws IOException { + protected Object decode(final String line) throws IOException { final String[] splitLine = line.split("\\s+"); return splitLine[1]; } @@ -376,12 +410,14 @@ String encode(final Object object) { throw new TribbleException("Cannot encode null in VERSION3_DIRECTIVE"); } if (!(object instanceof String)) { - throw new TribbleException("Cannot encode object of type " + object.getClass() + " in VERSION3_DIRECTIVE"); + throw new TribbleException("Cannot encode object of type " + object.getClass() + + " in VERSION3_DIRECTIVE"); } - final String versionLine = "##gff-version " + (String)object; + final String versionLine = "##gff-version " + (String) object; if (!regexPattern.matcher(versionLine).matches()) { - throw new TribbleException("Version " + (String)object + " is not a valid version"); + throw new TribbleException( + "Version " + (String) object + " is not a valid version"); } return versionLine; @@ -392,6 +428,7 @@ String encode(final Object object) { final private int CONTIG_INDEX = 1; final private int START_INDEX = 2; final private int END_INDEX = 3; + @Override protected Object decode(final String line) throws IOException { final String[] splitLine = line.split("\\s+"); @@ -407,11 +444,13 @@ String encode(final Object object) { throw new TribbleException("Cannot encode null in SEQUENCE_REGION_DIRECTIVE"); } if (!(object instanceof SequenceRegion)) { - throw new TribbleException("Cannot encode object of type " + object.getClass() + " in SEQUENCE_REGION_DIRECTIVE"); + throw new TribbleException("Cannot encode object of type " + object.getClass() + + " in SEQUENCE_REGION_DIRECTIVE"); } final SequenceRegion sequenceRegion = (SequenceRegion) object; - return "##sequence-region " + Gff3Writer.encodeString(sequenceRegion.getContig()) + " " + sequenceRegion.getStart() + " " + sequenceRegion.getEnd(); + return "##sequence-region " + Gff3Writer.encodeString(sequenceRegion.getContig()) + + " " + sequenceRegion.getStart() + " " + sequenceRegion.getEnd(); } }, @@ -437,7 +476,7 @@ String encode(final Object object) { public static Gff3Directive toDirective(final String line) { for (final Gff3Directive directive : Gff3Directive.values()) { - if(directive.regexPattern.matcher(line).matches()) { + if (directive.regexPattern.matcher(line).matches()) { return directive; } } diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Constants.java b/src/main/java/htsjdk/tribble/gff/Gff3Constants.java index 5e9f231959..a55eb7046e 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Constants.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Constants.java @@ -1,10 +1,10 @@ package htsjdk.tribble.gff; public class Gff3Constants extends AbstractGxxConstants { - public static final char KEY_VALUE_SEPARATOR = '='; - public static final char VALUE_DELIMITER = ','; - public static final String PARENT_ATTRIBUTE_KEY = "Parent"; - public static final String ID_ATTRIBUTE_KEY = "ID"; - public static final String NAME_ATTRIBUTE_KEY = "Name"; - public static final String ALIAS_ATTRIBUTE_KEY = "Alias"; + public static final char KEY_VALUE_SEPARATOR = '='; + public static final char VALUE_DELIMITER = ','; + public static final String PARENT_ATTRIBUTE_KEY = "Parent"; + public static final String ID_ATTRIBUTE_KEY = "ID"; + public static final String NAME_ATTRIBUTE_KEY = "Name"; + public static final String ALIAS_ATTRIBUTE_KEY = "Alias"; } diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Feature.java b/src/main/java/htsjdk/tribble/gff/Gff3Feature.java index 37a879a5b9..3850a49f69 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Feature.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Feature.java @@ -9,13 +9,18 @@ import java.util.Set; /** - * Gff3 format spec is defined at https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md - * Discontinuous features which are split between multiple lines in the gff files are implemented as separate features linked as "co-features" + * Gff3 format spec is defined at + * https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md + * Discontinuous features which are split between multiple lines in the gff files are implemented as + * separate features linked as "co-features". + * + * This interface is also used for GTF */ public interface Gff3Feature extends Feature { /** - * Get the set of top level features from which this feature is descended. - * Top level features are features with no linked parents + * Get the set of top level features from which this feature is descended. Top level features + * are features with no linked parents + * * @return set of top level feature from which this feature is descended */ Set getTopLevelFeatures(); @@ -40,14 +45,16 @@ default int getPhase() { return getBaseData().getPhase(); } - default String getType() {return getBaseData().getType();} + default String getType() { + return getBaseData().getType(); + } @Override default String getContig() { return getBaseData().getContig(); } - @Override + @Override default int getStart() { return getBaseData().getStart(); } @@ -56,7 +63,7 @@ default int getStart() { default List getAttribute(final String key) { return getBaseData().getAttribute(key); } - + /** * Returns true if this record contains an attribute for the specified key. * @@ -68,70 +75,93 @@ default boolean hasAttribute(final String key) { } /** - * Most attributes in a GFF file are present just one time in a line, e.g. : gene_biotype, gene_name, etc ... - * This function returns an Optional.empty if the key is not present, - * an Optional.of(value) if there is only one value associated to the key, - * or it throws an IllegalArgumentException if there is more than one value. + * Most attributes in a GFF file are present just one time in a line, e.g. : + * gene_biotype, gene_name, etc ... This function returns an + * Optional.empty if the key is not present, an Optional.of(value) if + * there is only one value associated to the key, or it throws an + * IllegalArgumentException if there is more than one value. * * @param key key whose presence in the attributes is to be tested - * @return Optional<String> if this map contains zero or one attribute for the specified key + * @return Optional<String> if this map contains zero or one attribute for the + * specified key * @throws IllegalArgumentException if there is more than one value. */ default Optional getUniqueAttribute(final String key) { - return getBaseData().getUniqueAttribute(key); + return getBaseData().getUniqueAttribute(key); } - - default Map> getAttributes() { return getBaseData().getAttributes();} - default String getID() { return getBaseData().getId();} + default Map> getAttributes() { + return getBaseData().getAttributes(); + } - default String getName() { return getBaseData().getName();} + default String getID() { + return getBaseData().getId(); + } - default List getAliases() { return getBaseData().getAliases();} + default String getName() { + return getBaseData().getName(); + } - default double getScore() { return getBaseData().getScore();} + default List getAliases() { + return getBaseData().getAliases(); + } + + default double getScore() { + return getBaseData().getScore(); + } /** * Get BaseData object which contains all the basic information of the feature + * * @return */ Gff3BaseData getBaseData(); /** * Gets set of parent features + * * @return set of parent features */ Set getParents(); /** * Gets set of features for which this feature is a parent + * * @return set of child features */ Set getChildren(); /** - * Get set of all features this feature descends from, through chains of Parent attributes. If Derives_From exists for this feature, - * then only features along the inheritance path specified by the Derives_From attribute should be included as ancestors of this feature + * Get set of all features this feature descends from, through chains of Parent attributes. If + * Derives_From exists for this feature, then only features along the inheritance path specified + * by the Derives_From attribute should be included as ancestors of this feature + * * @return set of ancestor features */ Set getAncestors(); /** - * Get set of all features descended from this features, through chains of Parent attributes. If Derives_From attribute exists for a feature, - * it should only be included as a descendent of this feature if the inheritance path specified by its Derives_From attribute includes this feature + * Get set of all features descended from this features, through chains of Parent attributes. If + * Derives_From attribute exists for a feature, it should only be included as a descendent of + * this feature if the inheritance path specified by its Derives_From attribute includes this + * feature + * * @return set of descendents */ Set getDescendents(); /** - * Get set of co-features. Co-features correspond to the other lines in the gff file that together make up a single discontinuous feature + * Get set of co-features. Co-features correspond to the other lines in the gff file that + * together make up a single discontinuous feature + * * @return set of co-features */ Set getCoFeatures(); /*** - * Flatten this feature and all descendents into a set of features. The Derives_From attribute is respected if it exists - * for this feature + * Flatten this feature and all descendents into a set of features. The Derives_From attribute + * is respected if it exists for this feature + * * @return set of this feature and all descendents */ Set flatten(); diff --git a/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java b/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java index 6a7de7a447..fe54521bc1 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3FeatureImpl.java @@ -8,8 +8,10 @@ import java.util.stream.Collectors; /** - * Gff3 format spec is defined at https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md - * Discontinuous features which are split between multiple lines in the gff files are implemented as separate features linked as "co-features" + * Gff3 format spec is defined at + * https://github.com/The-Sequence-Ontology/Specifications/blob/31f62ad469b31769b43af42e0903448db1826925/gff3.md + * Discontinuous features which are split between multiple lines in the gff files are implemented as + * separate features linked as "co-features" */ public class Gff3FeatureImpl implements Gff3Feature { private final static String DERIVES_FROM_ATTRIBUTE_KEY = "Derives_from"; @@ -24,15 +26,16 @@ public class Gff3FeatureImpl implements Gff3Feature { private final LinkedHashSet coFeatures = new LinkedHashSet<>(); /** - * top level features are features with no parents. Each feature maintains a list - * of its top level features, from which it and all related features descend. + * top level features are features with no parents. Each feature maintains a list of its top + * level features, from which it and all related features descend. */ private final Set topLevelFeatures = new HashSet<>(); public Gff3FeatureImpl(final String contig, final String source, final String type, - final int start, final int end, final Double score, final Strand strand, final int phase, - final Map> attributes) { - baseData = new Gff3BaseData(contig, source, type, start, end, score, strand, phase, attributes); + final int start, final int end, final Double score, final Strand strand, + final int phase, final Map> attributes) { + baseData = new Gff3BaseData(contig, source, type, start, end, score, strand, phase, + attributes); } @@ -42,6 +45,7 @@ public Gff3FeatureImpl(final Gff3BaseData baseData) { /** * Get the set of top level features from which this feature is descended + * * @return set of top level feature from which this feature is descended */ @Override @@ -59,17 +63,23 @@ public boolean isTopLevelFeature() { /** * Gets set of parent features + * * @return set of parent features */ @Override - public Set getParents() {return parents;} + public Set getParents() { + return parents; + } /** * Gets set of features for which this feature is a parent + * * @return set of child features */ @Override - public Set getChildren() {return children;} + public Set getChildren() { + return children; + } @Override public Gff3BaseData getBaseData() { @@ -77,14 +87,20 @@ public Gff3BaseData getBaseData() { } /** - * Get set of all features this feature descends from, through chains of Parent attributes. Derives_From can be used to specify a particular inheritance path for this feature when multiple paths are available + * Get set of all features this feature descends from, through chains of Parent attributes. + * Derives_From can be used to specify a particular inheritance path for this feature when + * multiple paths are available + * * @return set of ancestor features */ @Override public Set getAncestors() { final List ancestors = new ArrayList<>(parents); for (final Gff3FeatureImpl parent : parents) { - ancestors.addAll(getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).isEmpty()? parent.getAncestors() : parent.getAncestors(new HashSet<>(baseData.getAttributes().get(DERIVES_FROM_ATTRIBUTE_KEY)))); + ancestors.addAll( + getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).isEmpty() ? parent.getAncestors() + : parent.getAncestors(new HashSet<>( + baseData.getAttributes().get(DERIVES_FROM_ATTRIBUTE_KEY)))); } return new LinkedHashSet<>(ancestors); } @@ -92,7 +108,8 @@ public Set getAncestors() { private Set getAncestors(final Collection derivingFrom) { final List ancestors = new ArrayList<>(); for (final Gff3FeatureImpl parent : parents) { - if (derivingFrom.contains(parent.getID()) || parent.getAncestors().stream().anyMatch(f -> derivingFrom.contains(f.getID()))) { + if (derivingFrom.contains(parent.getID()) || parent.getAncestors().stream() + .anyMatch(f -> derivingFrom.contains(f.getID()))) { ancestors.add(parent); ancestors.addAll(parent.getAncestors()); } @@ -101,7 +118,10 @@ private Set getAncestors(final Collection derivingFrom) } /** - * Get set of all features descended from this features, through chains of Parent attributes. Derives_From can be used to specify a particular inheritance path for this feature when multiple paths are available + * Get set of all features descended from this features, through chains of Parent attributes. + * Derives_From can be used to specify a particular inheritance path for this feature when + * multiple paths are available + * * @return set of descendents */ @Override @@ -109,20 +129,22 @@ public Set getDescendents() { final List descendants = new ArrayList<>(children); final Set idsInLineage = new HashSet<>(Collections.singleton(baseData.getId())); idsInLineage.addAll(children.stream().map(Gff3Feature::getID).collect(Collectors.toSet())); - for(final Gff3FeatureImpl child : children) { + for (final Gff3FeatureImpl child : children) { descendants.addAll(child.getDescendents(idsInLineage)); } return new LinkedHashSet<>(descendants); } private Set getDescendents(final Set idsInLineage) { - final List childrenToAdd = children.stream().filter(c -> c.getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).isEmpty() || - !Collections.disjoint(idsInLineage, c.getAttribute(DERIVES_FROM_ATTRIBUTE_KEY))). - collect(Collectors.toList()); + final List childrenToAdd = children.stream() + .filter(c -> c.getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).isEmpty() || !Collections + .disjoint(idsInLineage, c.getAttribute(DERIVES_FROM_ATTRIBUTE_KEY))) + .collect(Collectors.toList()); final List descendants = new ArrayList<>(childrenToAdd); final Set updatedIdsInLineage = new HashSet<>(idsInLineage); - updatedIdsInLineage.addAll(childrenToAdd.stream().map(Gff3Feature::getID).collect(Collectors.toSet())); + updatedIdsInLineage + .addAll(childrenToAdd.stream().map(Gff3Feature::getID).collect(Collectors.toSet())); for (final Gff3FeatureImpl child : childrenToAdd) { descendants.addAll(child.getDescendents(updatedIdsInLineage)); } @@ -130,26 +152,40 @@ private Set getDescendents(final Set idsInLineage) { } /** - * Get set of co-features. Co-features correspond to the other lines in the gff file that together make up a single discontinuous feature + * Get set of co-features. Co-features correspond to the other lines in the gff file that + * together make up a single discontinuous feature + * * @return set of co-features */ @Override - public Set getCoFeatures() {return coFeatures;} + public Set getCoFeatures() { + return coFeatures; + } @Override - public boolean hasParents() {return !parents.isEmpty();} + public boolean hasParents() { + return !parents.isEmpty(); + } @Override - public boolean hasChildren() {return !children.isEmpty();} + public boolean hasChildren() { + return !children.isEmpty(); + } @Override - public boolean hasCoFeatures() {return !coFeatures.isEmpty();} + public boolean hasCoFeatures() { + return !coFeatures.isEmpty(); + } public void addParent(final Gff3FeatureImpl parent) { - final Set topLevelFeaturesToAdd = new HashSet<>(parent.getTopLevelFeatures()); + final Set topLevelFeaturesToAdd = + new HashSet<>(parent.getTopLevelFeatures()); if (!getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).isEmpty()) { - topLevelFeaturesToAdd.removeIf(f -> !getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).contains(f.getID()) && f.getDescendents().stream().noneMatch(f2 -> f2.getID()!= null && getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).contains(f2.getID()))); + topLevelFeaturesToAdd.removeIf(f -> !getAttribute(DERIVES_FROM_ATTRIBUTE_KEY) + .contains(f.getID()) + && f.getDescendents().stream().noneMatch(f2 -> f2.getID() != null + && getAttribute(DERIVES_FROM_ATTRIBUTE_KEY).contains(f2.getID()))); } parents.add(parent); parent.addChild(this); @@ -164,7 +200,7 @@ private void addChild(final Gff3FeatureImpl child) { private void addTopLevelFeatures(final Collection topLevelFeaturesToAdd) { topLevelFeatures.addAll(topLevelFeaturesToAdd); - //pass topLevelFeature change through to children + // pass topLevelFeature change through to children for (final Gff3FeatureImpl child : children) { child.addTopLevelFeatures(topLevelFeaturesToAdd); child.removeTopLevelFeature(this); @@ -180,15 +216,18 @@ private void removeTopLevelFeature(final Gff3FeatureImpl topLevelFeatureToRemove } /** - * Add a feature as a coFeature of this feature. When this method is called, the input coFeature will also be - * added as a coFeature of all the other coFeatures of this object, and this feature and all coFeatures will be - * added as coFeatures of the input coFeature. All coFeatures must have equal IDs and parents. + * Add a feature as a coFeature of this feature. When this method is called, the input coFeature + * will also be added as a coFeature of all the other coFeatures of this object, and this + * feature and all coFeatures will be added as coFeatures of the input coFeature. All coFeatures + * must have equal IDs and parents. + * * @param coFeature feature to add as this features coFeature */ public void addCoFeature(final Gff3FeatureImpl coFeature) { if (!parents.equals(coFeature.getParents())) { - throw new TribbleException("Co-features " + baseData.getId() + " do not have same parents"); + throw new TribbleException( + "Co-features " + baseData.getId() + " do not have same parents"); } for (final Gff3FeatureImpl feature : coFeatures) { feature.addCoFeatureShallow(coFeature); @@ -201,7 +240,8 @@ public void addCoFeature(final Gff3FeatureImpl coFeature) { private void addCoFeatureShallow(final Gff3FeatureImpl coFeature) { coFeatures.add(coFeature); if (!coFeature.getID().equals(baseData.getId())) { - throw new TribbleException("Attempting to add co-feature with id " + coFeature.getID() + " to feature with id " + baseData.getId()); + throw new TribbleException("Attempting to add co-feature with id " + coFeature.getID() + + " to feature with id " + baseData.getId()); } } @@ -213,38 +253,41 @@ public boolean equals(Object other) { if (!(other instanceof Gff3Feature)) { return false; } - /* to test for equality, the doubly linked list representation used to represent feature relationships is replaced with a graph representation. - equality for between two features is tested by testing equality between their base data fields, and equality between the graphs they are part of. + /* + * to test for equality, the doubly linked list representation used to represent feature + * relationships is replaced with a graph representation. equality for between two features + * is tested by testing equality between their base data fields, and equality between the + * graphs they are part of. */ - return baseData.equals(((Gff3Feature) other).getBaseData()) && - new Gff3Graph(this).equals(new Gff3Graph((Gff3Feature) other)); + return baseData.equals(((Gff3Feature) other).getBaseData()) + && new Gff3Graph(this).equals(new Gff3Graph((Gff3Feature) other)); } @Override public int hashCode() { - //hash only based on baseData, to keep immutable. + // hash only based on baseData, to keep immutable. return baseData.hashCode(); } - - /*** * flatten this feature and all descendents into a set of features + * * @return set of this feature and all descendents */ @Override public Set flatten() { - final LinkedHashSet features = new LinkedHashSet<>(Collections.singleton(this)); + final LinkedHashSet features = + new LinkedHashSet<>(Collections.singleton(this)); features.addAll(this.getDescendents()); return features; } /** - * Class for graph representation of relationships between features. - * Used for testing equality between features + * Class for graph representation of relationships between features. Used for testing equality + * between features */ private static class Gff3Graph { final private Set nodes = new HashSet<>(); @@ -253,7 +296,8 @@ private static class Gff3Graph { final private Set> coFeatureSets = new HashSet<>(); Gff3Graph(final Gff3Feature feature) { - feature.getTopLevelFeatures().stream().flatMap(f -> f.flatten().stream()).forEach(this::addFeature); + feature.getTopLevelFeatures().stream().flatMap(f -> f.flatten().stream()) + .forEach(this::addFeature); } private void addFeature(final Gff3Feature feature) { @@ -268,20 +312,21 @@ private void addNode(final Gff3Feature feature) { } private void addParentEdges(final Gff3Feature feature) { - for(final Gff3Feature parent : feature.getParents()) { + for (final Gff3Feature parent : feature.getParents()) { parentEdges.add(new Tuple<>(feature.getBaseData(), parent.getBaseData())); } } private void addChildEdges(final Gff3Feature feature) { - for(final Gff3Feature child : feature.getChildren()) { + for (final Gff3Feature child : feature.getChildren()) { childEdges.add(new Tuple<>(feature.getBaseData(), child.getBaseData())); } } private void addCoFeatureSet(final Gff3Feature feature) { if (feature.hasCoFeatures()) { - final Set coFeaturesBaseData = feature.getCoFeatures().stream().map(Gff3Feature::getBaseData).collect(Collectors.toSet()); + final Set coFeaturesBaseData = feature.getCoFeatures().stream() + .map(Gff3Feature::getBaseData).collect(Collectors.toSet()); coFeaturesBaseData.add(feature.getBaseData()); coFeatureSets.add(coFeaturesBaseData); } @@ -296,10 +341,10 @@ public boolean equals(Object other) { return false; } - return nodes.equals(((Gff3Graph) other).nodes) && - parentEdges.equals(((Gff3Graph) other).parentEdges) && - childEdges.equals(((Gff3Graph) other).childEdges) && - coFeatureSets.equals(((Gff3Graph) other).coFeatureSets); + return nodes.equals(((Gff3Graph) other).nodes) + && parentEdges.equals(((Gff3Graph) other).parentEdges) + && childEdges.equals(((Gff3Graph) other).childEdges) + && coFeatureSets.equals(((Gff3Graph) other).coFeatureSets); } @Override @@ -313,12 +358,10 @@ public int hashCode() { } } - @Override - public String toString() { - return "Gff3FeatureImpl [baseData=" + baseData + - " , parents:" + this.parents.size() + - " , children:" + this.children.size() + - "]"; - } + @Override + public String toString() { + return "Gff3FeatureImpl [baseData=" + baseData + " , parents:" + this.parents.size() + + " , children:" + this.children.size() + "]"; + } -} \ No newline at end of file +} diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Writer.java b/src/main/java/htsjdk/tribble/gff/Gff3Writer.java index ba5139e038..4ce7581982 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Writer.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Writer.java @@ -11,8 +11,10 @@ /** - * A class to write out gff3 files. Features are added using {@link #addFeature(Gff3Feature)}, directives using {@link #addDirective(Gff3Codec.Gff3Directive)}, - * and comments using {@link #addComment(String)}. Note that the version 3 directive is automatically added at creation, so should not be added separately. + * A class to write out gff3 files. Features are added using {@link #addFeature(Gff3Feature)}, + * directives using {@link #addDirective(Gff3Codec.Gff3Directive)}, and comments using + * {@link #addComment(String)}. Note that the version 3 directive is automatically added at + * creation, so should not be added separately. */ public class Gff3Writer extends AbstractGxxWriter { @@ -21,7 +23,7 @@ public class Gff3Writer extends AbstractGxxWriter { public Gff3Writer(final Path path) throws IOException { super(path, FileExtensions.GFF3); initialize(); - } + } public Gff3Writer(final OutputStream stream) { super(stream); @@ -36,48 +38,55 @@ private void initialize() { } } - + @Override protected void writeAttributes(final Map> attributes) throws IOException { if (attributes.isEmpty()) { out.write(Gff3Constants.UNDEFINED_FIELD_VALUE.getBytes()); } - writeJoinedByDelimiter(Gff3Constants.ATTRIBUTE_DELIMITER, e -> writeKeyValuePair(e.getKey(), e.getValue()), attributes.entrySet()); + writeJoinedByDelimiter(Gff3Constants.ATTRIBUTE_DELIMITER, + e -> writeKeyValuePair(e.getKey(), e.getValue()), attributes.entrySet()); } void writeKeyValuePair(final String key, final List values) { try { tryToWrite(key); out.write(Gff3Constants.KEY_VALUE_SEPARATOR); - writeJoinedByDelimiter(Gff3Constants.VALUE_DELIMITER, v -> tryToWrite(escapeString(v)), values); + writeJoinedByDelimiter(Gff3Constants.VALUE_DELIMITER, v -> tryToWrite(escapeString(v)), + values); } catch (final IOException ex) { throw new TribbleException("error writing out key value pair " + key + " " + values); } } - + /** * Add a directive with an object + * * @param directive the directive to be added * @param object the object to be encoded with the directive * @throws IOException */ - public void addDirective(final Gff3Codec.Gff3Directive directive, final Object object) throws IOException { + public void addDirective(final Gff3Codec.Gff3Directive directive, final Object object) + throws IOException { if (directive == Gff3Codec.Gff3Directive.VERSION3_DIRECTIVE) { - throw new TribbleException("VERSION3_DIRECTIVE is automatically added and should not be added manually."); + throw new TribbleException( + "VERSION3_DIRECTIVE is automatically added and should not be added manually."); } writeWithNewLine(directive.encode(object)); } /** * Add a directive + * * @param directive the directive to be added * @throws IOException */ public void addDirective(final Gff3Codec.Gff3Directive directive) throws IOException { if (directive == Gff3Codec.Gff3Directive.VERSION3_DIRECTIVE) { - throw new TribbleException("VERSION3_DIRECTIVE is automatically added and should not be added manually."); + throw new TribbleException( + "VERSION3_DIRECTIVE is automatically added and should not be added manually."); } addDirective(directive, null); } -} \ No newline at end of file +} diff --git a/src/main/java/htsjdk/tribble/gff/GtfCodec.java b/src/main/java/htsjdk/tribble/gff/GtfCodec.java index b56b880060..5b3fc1d100 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfCodec.java +++ b/src/main/java/htsjdk/tribble/gff/GtfCodec.java @@ -27,15 +27,20 @@ /** * Codec for parsing GTF files + * @author Pierre Lindenbaum + * + * This codec reads GTF file. + * In `DecodeDepth.DEEP` mode, the features with type="gene" have children with type="transcript|mrna" and `gene_id` linked to the gene. + * Other features having an attribute `transcript_id` linked to their transcript. */ public class GtfCodec extends AbstractGxxCodec { - + private final static Log logger = Log.getInstance(GtfCodec.class); - private final Map id2gene = new HashMap<>(); - private final Map id2transcripts = new HashMap<>(); - private final Map> id2TranscriptComponents = new HashMap<>(); - + private final Map id2gene = new HashMap<>(); + private final Map id2transcripts = new HashMap<>(); + private final Map> id2TranscriptComponents = new HashMap<>(); + public GtfCodec() { this(DecodeDepth.DEEP); } @@ -43,105 +48,117 @@ public GtfCodec() { public GtfCodec(final DecodeDepth decodeDepth) { this(decodeDepth, KEY -> false); } + /** * @param decodeDepth a value from DecodeDepth - * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column + * @param filterOutAttribute filter to remove keys from the EXTRA_FIELDS column */ public GtfCodec(final DecodeDepth decodeDepth, final Predicate filterOutAttribute) { - super(decodeDepth,filterOutAttribute); + super(decodeDepth, filterOutAttribute); /* check required keys are always kept */ for (final String key : new String[] {GtfConstants.GENE_ID, GtfConstants.TRANSCRIPT_ID}) { if (filterOutAttribute.test(key)) { throw new IllegalArgumentException("Predicate should always accept " + key); - } + } } - } + } + /** test if a feature is a gene */ private boolean isGene(final Gff3Feature feature) { - return feature.getType().equals("gene"); + return feature.getType().equals("gene"); } - + + /** test if a feature is a transcript */ private boolean isTranscript(final Gff3Feature feature) { - return feature.getType().equals("transcript") || feature.getType().equalsIgnoreCase("mrna") ; + return feature.getType().equals("transcript") || feature.getType().equalsIgnoreCase("mrna"); } - - - @Override - protected Gff3Feature decode(LineIterator lineIterator, DecodeDepth depth) throws IOException { + + @Override + protected Gff3Feature decode(final LineIterator lineIterator, DecodeDepth depth) throws IOException { currentLine++; /* - Basic strategy: Load features into deque, create maps from a features ID to it, and from a features parents' IDs to it. For each feature, link to parents using these maps. - When reaching flush directive, fasta, or end of file, prepare to flush features by moving all active features to deque of features to flush, and clearing - list of active features and both maps. Always poll featuresToFlush to return any completed top level features. + * Basic strategy: Load features into deque, create maps for gene and transcript. + * For each feature, link to parents using these maps. When + * reaching flush directive, or end of file, prepare to flush features by moving all + * active features to deque of features to flush, and clearing list of active features and + * both maps. Always poll featuresToFlush to return any completed top level features. */ if (!lineIterator.hasNext()) { - //no more lines, flush whatever is active + // no more lines, flush whatever is active prepareToFlushFeatures(); return featuresToFlush.poll(); } final String line = lineIterator.next(); - if (line.startsWith(GtfConstants.COMMENT_START)) { - commentsWithLineNumbers.put(currentLine, line.substring(GtfConstants.COMMENT_START.length())); + commentsWithLineNumbers.put(currentLine, + line.substring(GtfConstants.COMMENT_START.length())); return featuresToFlush.poll(); } - - final Gff3FeatureImpl thisFeature = new Gff3FeatureImpl(parseLine(line, currentLine, super.filterOutAttribute)); + final Gff3FeatureImpl thisFeature = + new Gff3FeatureImpl(parseLine(line, currentLine, super.filterOutAttribute)); super.activeFeatures.add(thisFeature); - - switch(depth) { - case SHALLOW: - //flush all features immediatly - prepareToFlushFeatures(); - break; - case DEEP: - if(isGene(thisFeature)) { - final String gene_id = thisFeature.getUniqueAttribute(GtfConstants.GENE_ID).orElseThrow(()-> - new TribbleException("attribute "+GtfConstants.GENE_ID+" missing in "+line) - ); - if(id2gene.containsKey(gene_id)) { - throw new TribbleException("duplicate gene "+ gene_id +" in "+line); - } - id2gene.put(gene_id, thisFeature); - } else if(isTranscript(thisFeature)) { - final String transcript_id = thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> - new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+line) - ); - if(id2transcripts.containsKey(transcript_id)) { - throw new TribbleException("duplicate transcript "+ transcript_id +" in "+line); - } - id2transcripts.put(transcript_id, thisFeature); - } else if(thisFeature.hasAttribute(GtfConstants.TRANSCRIPT_ID)){ - final String transcript_id = thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> - new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+line) - ); - List components =this.id2TranscriptComponents.get(transcript_id); - if(components==null) { - components = new ArrayList<>(); - this.id2TranscriptComponents.put(transcript_id, components); - } - components.add(thisFeature); - } - break; + + switch (depth) { + case SHALLOW: + // flush all features immediatly + prepareToFlushFeatures(); + break; + case DEEP: + if (isGene(thisFeature)) { + final String gene_id = thisFeature.getUniqueAttribute(GtfConstants.GENE_ID) + .orElseThrow(() -> new TribbleException( + "attribute " + GtfConstants.GENE_ID + " missing in " + line)); + if (id2gene.containsKey(gene_id)) { + throw new TribbleException("duplicate gene " + gene_id + " in " + line); + } + id2gene.put(gene_id, thisFeature); + } else if (isTranscript(thisFeature)) { + final String transcript_id = + thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID) + .orElseThrow(() -> new TribbleException("attribute " + + GtfConstants.TRANSCRIPT_ID + " missing in " + line)); + if (id2transcripts.containsKey(transcript_id)) { + throw new TribbleException( + "duplicate transcript " + transcript_id + " in " + line); + } + id2transcripts.put(transcript_id, thisFeature); + } else if (thisFeature.hasAttribute(GtfConstants.TRANSCRIPT_ID)) { + final String transcript_id = + thisFeature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID) + .orElseThrow(() -> new TribbleException("attribute " + + GtfConstants.TRANSCRIPT_ID + " missing in " + line)); + List components = + this.id2TranscriptComponents.get(transcript_id); + if (components == null) { + components = new ArrayList<>(); + this.id2TranscriptComponents.put(transcript_id, components); + } + components.add(thisFeature); + } + break; + default: throw new IllegalStateException(depth.name()); } - + return featuresToFlush.poll(); } - @Override - protected Map> parseAttributesColumn(final String attributesString) throws UnsupportedEncodingException { - return parseAttributes(attributesString); - } - - static Map> parseAttributes(final String attributesString) throws UnsupportedEncodingException { - if (attributesString.trim().equals(GtfConstants.UNDEFINED_FIELD_VALUE)) { + @Override + protected Map> parseAttributesColumn(final String attributesString) + throws UnsupportedEncodingException { + return parseAttributes(attributesString); + } + + /** parse attributes for GTF */ + static Map> parseAttributes(final String attributesString) + throws UnsupportedEncodingException { + if (attributesString.trim().equals(GtfConstants.UNDEFINED_FIELD_VALUE)) { return Collections.emptyMap(); - } + } final Map> attributes = new LinkedHashMap<>(); - + final int len = attributesString.length(); int i = 0; for (;;) { @@ -149,20 +166,22 @@ static Map> parseAttributes(final String attributesString) while (i < len && Character.isWhitespace(attributesString.charAt(i))) { i++; } - + // end of string if (i >= len) { break; } - + final StringBuilder keyBuilder = new StringBuilder(); // consumme key while (i < len && !Character.isWhitespace(attributesString.charAt(i))) { - char c = attributesString.charAt(i); - if(c==Gff3Constants.KEY_VALUE_SEPARATOR) { - throw new TribbleException("unexpected gff3 separator "+Gff3Constants.KEY_VALUE_SEPARATOR+" in gtf line "+attributesString); - } + char c = attributesString.charAt(i); + if (c == Gff3Constants.KEY_VALUE_SEPARATOR) { + throw new TribbleException( + "unexpected gff3 separator " + Gff3Constants.KEY_VALUE_SEPARATOR + + " in gtf line " + attributesString); + } keyBuilder.append(c); i++; } @@ -175,121 +194,138 @@ static Map> parseAttributes(final String attributesString) /* read VALUE */ final StringBuilder valueBuilder = new StringBuilder(); - + // no value if (i >= len) { logger.warn("no value for '" + key + "' in " + attributesString); - } - else - { - // first char of value - char c = attributesString.charAt(i); - - if (c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { // no value - logger.warn("no value for '" + key + "' in " + attributesString); - } - else if(c==Gff3Constants.KEY_VALUE_SEPARATOR) { - throw new TribbleException("unexpected gff3 separator '"+Gff3Constants.KEY_VALUE_SEPARATOR+"' in gtf line "+attributesString); - } - // quoted string - else if (c == '\"' || c=='\'') { - final char quote_symbol = c; - i++; - for(;;) { - if(i>=len) throw new TribbleException("unclosed quoted string in "+attributesString); - c = attributesString.charAt(i); - if (c == '\\') { - if(i+1>=len) throw new TribbleException("unclosed escape symbol in "+attributesString); - ++i; - c = attributesString.charAt(i); - switch (c) { - case '"': valueBuilder.append("\""); break; - case '\'': valueBuilder.append("\'"); break; - case 't': valueBuilder.append("\t"); break; - case 'n': valueBuilder.append("\n"); break; - default: logger.warn("unparsed escape symbol in " + attributesString); break; - } - i++; - } else if (c ==quote_symbol) { - i++; - break; - } else { - valueBuilder.append(c); - i++; - } - } // end of for - } - else /* not a quoted string, for example, a number */ - { - while (i < len) { - c = attributesString.charAt(i); - if(c==Gff3Constants.KEY_VALUE_SEPARATOR) { - throw new TribbleException("unexpected gff3 separator '"+Gff3Constants.KEY_VALUE_SEPARATOR+"' in gtf line "+attributesString); - } - if (Character.isWhitespace(c) || c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { - break; - } - valueBuilder.append(c); - i++; - } - } - }// end of else 'value' - + } else { + // first char of value + char c = attributesString.charAt(i); + + if (c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { // no value + logger.warn("no value for '" + key + "' in " + attributesString); + } else if (c == Gff3Constants.KEY_VALUE_SEPARATOR) { + throw new TribbleException( + "unexpected gff3 separator '" + Gff3Constants.KEY_VALUE_SEPARATOR + + "' in gtf line " + attributesString); + } + // quoted string + else if (c == '\"' || c == '\'') { + final char quote_symbol = c; + i++; + for (;;) { + if (i >= len) + throw new TribbleException( + "unclosed quoted string in " + attributesString); + c = attributesString.charAt(i); + if (c == '\\') { + if (i + 1 >= len) + throw new TribbleException( + "unclosed escape symbol in " + attributesString); + ++i; + c = attributesString.charAt(i); + switch (c) { + case '"': + valueBuilder.append("\""); + break; + case '\'': + valueBuilder.append("\'"); + break; + case 't': + valueBuilder.append("\t"); + break; + case 'n': + valueBuilder.append("\n"); + break; + default: + logger.warn("unparsed escape symbol in " + attributesString); + break; + } + i++; + } else if (c == quote_symbol) { + i++; + break; + } else { + valueBuilder.append(c); + i++; + } + } // end of for + } else /* not a quoted string, for example, a number */ + { + while (i < len) { + c = attributesString.charAt(i); + if (c == Gff3Constants.KEY_VALUE_SEPARATOR) { + throw new TribbleException("unexpected gff3 separator '" + + Gff3Constants.KEY_VALUE_SEPARATOR + "' in gtf line " + + attributesString); + } + if (Character.isWhitespace(c) + || c == AbstractGxxConstants.ATTRIBUTE_DELIMITER) { + break; + } + valueBuilder.append(c); + i++; + } + } + } // end of else 'value' + List values = attributes.get(key); - if(values==null) { - values = new ArrayList<>(); - attributes.put(key,values); - } - values.add( URLDecoder.decode(valueBuilder.toString(), "UTF-8")); - + if (values == null) { + values = new ArrayList<>(); + attributes.put(key, values); + } + values.add(URLDecoder.decode(valueBuilder.toString(), "UTF-8")); + // skip whitespaces while (i < len && Character.isWhitespace(attributesString.charAt(i))) { i++; - } + } if (i >= len) { break; - } - if(attributesString.charAt(i)!=AbstractGxxConstants.ATTRIBUTE_DELIMITER) { - throw new TribbleException("expected a '"+AbstractGxxConstants.ATTRIBUTE_DELIMITER+"' in gtf line before : \""+attributesString.substring(i)+"\". "+attributes); - } - i++; } - return attributes; + if (attributesString.charAt(i) != AbstractGxxConstants.ATTRIBUTE_DELIMITER) { + throw new TribbleException("expected a '" + AbstractGxxConstants.ATTRIBUTE_DELIMITER + + "' in gtf line before : \"" + attributesString.substring(i) + "\". " + + attributes); + } + i++; } + return attributes; + } - @Override - public boolean canDecode(final String inputFilePath) { + @Override + public boolean canDecode(final String inputFilePath) { try { // Simple file and name checks to start with: Path p = IOUtil.getPath(inputFilePath); - if(!FileExtensions.GTF.stream().anyMatch(fe -> p.toString().endsWith(fe))) { - return false; - } + if (!FileExtensions.GTF.stream().anyMatch(fe -> p.toString().endsWith(fe))) { + return false; + } // Crack open the file and look at the top of it: - final InputStream inputStream = IOUtil.hasGzipFileExtension(p)? new GZIPInputStream(Files.newInputStream(p)) : Files.newInputStream(p); - - try ( BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) ) { - String line; - for(;;) { - line = br.readLine(); - if ( line == null ) { - return false; - } - if(line.startsWith(GtfConstants.COMMENT_START)) { - continue; - } - return canDecodeFirstLine(line); - } + final InputStream inputStream = + IOUtil.hasGzipFileExtension(p) ? new GZIPInputStream(Files.newInputStream(p)) + : Files.newInputStream(p); + try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + for (;;) { + line = br.readLine(); + if (line == null) { + return false; + } + if (line.startsWith(GtfConstants.COMMENT_START)) { + continue; + } + return canDecodeFirstLine(line); } - - } - catch (final FileNotFoundException ex) { + + } + + } catch (final FileNotFoundException ex) { logger.error(inputFilePath + " not found."); return false; - } - catch (final IOException ex) { + } catch (final IOException ex) { return false; } @@ -297,60 +333,65 @@ public boolean canDecode(final String inputFilePath) { /** - * move active top level features to featuresToFlush. clear active features. + * move active top level features to featuresToFlush. clear active features. */ private void prepareToFlushFeatures() { - - this.id2transcripts.values().stream().forEach(FEAT->{ - final String gene_id = FEAT.getUniqueAttribute(GtfConstants.GENE_ID).orElseThrow(()-> - new TribbleException("attribute "+GtfConstants.GENE_ID+" missing in "+FEAT) - ); - if(!id2gene.containsKey(gene_id)) { - throw new TribbleException("undefined gene "+ gene_id +" in "+FEAT); - } - final Gff3FeatureImpl geneFeat = id2gene.get(gene_id); - FEAT.addParent(geneFeat); - }); - - this.id2TranscriptComponents.values().stream().flatMap(L->L.stream()).forEach(FEAT->{ - final String transcript_id = FEAT.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).orElseThrow(()-> - new TribbleException("attribute "+GtfConstants.TRANSCRIPT_ID+" missing in "+FEAT) - ); - if(!id2transcripts.containsKey(transcript_id)) { - throw new TribbleException("undefined transcript_id "+ transcript_id +" in "+FEAT); - } - final Gff3FeatureImpl transcriptFeat = id2transcripts.get(transcript_id); - FEAT.addParent(transcriptFeat); - }); - - + + this.id2transcripts.values().stream().forEach(FEAT -> { + final String gene_id = FEAT.getUniqueAttribute(GtfConstants.GENE_ID) + .orElseThrow(() -> new TribbleException( + "attribute " + GtfConstants.GENE_ID + " missing in " + FEAT)); + if (!id2gene.containsKey(gene_id)) { + throw new TribbleException("undefined gene " + gene_id + " in " + FEAT); + } + final Gff3FeatureImpl geneFeat = id2gene.get(gene_id); + FEAT.addParent(geneFeat); + }); + + this.id2TranscriptComponents.values().stream().flatMap(L -> L.stream()).forEach(FEAT -> { + final String transcript_id = FEAT.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID) + .orElseThrow(() -> new TribbleException( + "attribute " + GtfConstants.TRANSCRIPT_ID + " missing in " + FEAT)); + if (!id2transcripts.containsKey(transcript_id)) { + throw new TribbleException( + "undefined transcript " + transcript_id + " in " + FEAT); + } + final Gff3FeatureImpl transcriptFeat = id2transcripts.get(transcript_id); + + /* check they share the same gene_id */ + if(transcriptFeat.hasAttribute(GtfConstants.GENE_ID) && FEAT.hasAttribute(GtfConstants.GENE_ID) && + !transcriptFeat.getUniqueAttribute(GtfConstants.GENE_ID).get().equals(FEAT.getUniqueAttribute(GtfConstants.GENE_ID).get()) ) { + throw new TribbleException( + "same transcript_id bit no the same gene_id" + transcriptFeat + " and " + FEAT); + } + + FEAT.addParent(transcriptFeat); + }); + + featuresToFlush.addAll(activeFeatures); - id2transcripts.clear(); - id2gene.clear(); - id2TranscriptComponents.clear(); - super.activeFeatures.clear(); + id2transcripts.clear(); + id2gene.clear(); + id2TranscriptComponents.clear(); + super.activeFeatures.clear(); } - - - @Override - public boolean isDone(LineIterator lineIterator) { - return !lineIterator.hasNext() && - id2gene.isEmpty() && - id2transcripts.isEmpty() && - id2TranscriptComponents.isEmpty() && - featuresToFlush.isEmpty() && - super.activeFeatures.isEmpty(); - } - - @Override - public void close(LineIterator source) { - id2gene.clear(); - id2transcripts.clear(); - id2TranscriptComponents.clear(); - featuresToFlush.clear(); - activeFeatures.clear(); - CloserUtil.close(source); - } + + @Override + public boolean isDone(LineIterator lineIterator) { + return !lineIterator.hasNext() && id2gene.isEmpty() && id2transcripts.isEmpty() + && id2TranscriptComponents.isEmpty() && featuresToFlush.isEmpty() + && super.activeFeatures.isEmpty(); + } + + @Override + public void close(LineIterator source) { + id2gene.clear(); + id2transcripts.clear(); + id2TranscriptComponents.clear(); + featuresToFlush.clear(); + activeFeatures.clear(); + CloserUtil.close(source); + } } diff --git a/src/main/java/htsjdk/tribble/gff/GtfConstants.java b/src/main/java/htsjdk/tribble/gff/GtfConstants.java index 6cc94f46f3..b0ade09e23 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfConstants.java +++ b/src/main/java/htsjdk/tribble/gff/GtfConstants.java @@ -1,7 +1,11 @@ package htsjdk.tribble.gff; - +/** + * Constants for the GTF format + * @author Pierre Lindenbaum + * + */ public class GtfConstants extends AbstractGxxConstants { - public static final char VALUE_DELIMITER = ' '; - public static final String GENE_ID = "gene_id"; - public static final String TRANSCRIPT_ID = "transcript_id"; + public static final char VALUE_DELIMITER = ' '; + public static final String GENE_ID = "gene_id"; + public static final String TRANSCRIPT_ID = "transcript_id"; } diff --git a/src/main/java/htsjdk/tribble/gff/GtfWriter.java b/src/main/java/htsjdk/tribble/gff/GtfWriter.java index d19bb49688..6ae041c17b 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfWriter.java +++ b/src/main/java/htsjdk/tribble/gff/GtfWriter.java @@ -10,35 +10,36 @@ /** - * A class to write out gtf files. - * and comments using {@link #addComment(String)}. + * A class to write out gtf files. and comments using {@link #addComment(String)}. + * @author Pierre Lindenbaum */ public class GtfWriter extends AbstractGxxWriter { - public GtfWriter(final Path path) throws IOException { + public GtfWriter(final Path path) throws IOException { super(path, FileExtensions.GTF); - } + } public GtfWriter(final OutputStream stream) { super(stream); } - - + + @Override protected void writeAttributes(final Map> attributes) throws IOException { if (attributes.isEmpty()) { out.write(GtfConstants.UNDEFINED_FIELD_VALUE.getBytes()); } boolean first = true; - for(final String key: attributes.keySet()) { - for(String value : attributes.get(key)) { - if(!first) out.write(GtfConstants.ATTRIBUTE_DELIMITER); - out.write(escapeString(key).getBytes()); - out.write(GtfConstants.VALUE_DELIMITER); - out.write(escapeString(value).getBytes()); - first = false; - } + for (final String key : attributes.keySet()) { + for (String value : attributes.get(key)) { + if (!first) + out.write(GtfConstants.ATTRIBUTE_DELIMITER); + out.write(escapeString(key).getBytes()); + out.write(GtfConstants.VALUE_DELIMITER); + out.write(escapeString(value).getBytes()); + first = false; + } } - } -} \ No newline at end of file + } +} diff --git a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java index a4026ab490..7ff91f6f53 100644 --- a/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java +++ b/src/test/java/htsjdk/tribble/gff/GtfCodecTest.java @@ -29,142 +29,169 @@ public class GtfCodecTest extends HtsjdkTest { @DataProvider(name = "basicDecodeDataProvider") Object[][] basicDecodeDataProvider() { - return new Object[][]{ - {gencode47_gzipped, 842}, - {gencode47_PCSK9, 248} - }; + return new Object[][] {{gencode47_gzipped, 842}, {gencode47_PCSK9, 248}}; } - private void basicDecodeTest(final Path inputGtf, GtfCodec.DecodeDepth decodeDepth, final int expectedCount) throws IOException { + private void basicDecodeTest(final Path inputGtf, GtfCodec.DecodeDepth decodeDepth, + final int expectedCount) throws IOException { final GtfCodec codec = new GtfCodec(decodeDepth); - Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); - - try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( - inputGtf.toAbsolutePath().toString(), null, codec, false)) { - int countTotalFeatures = 0; - for (final Gff3Feature feature : reader.iterator()) { - countTotalFeatures++; - } - Assert.assertEquals(countTotalFeatures, expectedCount); - } + Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); + + try (final AbstractFeatureReader reader = AbstractFeatureReader + .getFeatureReader(inputGtf.toAbsolutePath().toString(), null, codec, false)) { + int countTotalFeatures = 0; + for (final Gff3Feature feature : reader.iterator()) { + countTotalFeatures++; + } + Assert.assertEquals(countTotalFeatures, expectedCount); + } } - + @Test(dataProvider = "basicDecodeDataProvider") - public void basicDecodeDeepTest(final Path inputGtf, final int expectedCount) throws IOException { - basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.DEEP,expectedCount); + public void basicDecodeDeepTest(final Path inputGtf, final int expectedCount) + throws IOException { + basicDecodeTest(inputGtf, GtfCodec.DecodeDepth.DEEP, expectedCount); } @Test(dataProvider = "basicDecodeDataProvider") - public void basicDecodeShallowTest(final Path inputGtf,final int expectedCount) throws IOException { - basicDecodeTest(inputGtf,GtfCodec.DecodeDepth.SHALLOW,expectedCount); + public void basicDecodeShallowTest(final Path inputGtf, final int expectedCount) + throws IOException { + basicDecodeTest(inputGtf, GtfCodec.DecodeDepth.SHALLOW, expectedCount); } - + @Test(dataProvider = "basicDecodeDataProvider") - public void codecFilterOutFieldsTest(final Path inputGtf, int expectedTotalFeatures) throws IOException { - final Set skip_attributes = new HashSet<>(Arrays.asList("tag","havana_gene")); - final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW, S->skip_attributes.contains(S)); + public void codecFilterOutFieldsTest(final Path inputGtf, int expectedTotalFeatures) + throws IOException { + final Set skip_attributes = new HashSet<>(Arrays.asList("tag", "havana_gene")); + final GtfCodec codec = + new GtfCodec(GtfCodec.DecodeDepth.SHALLOW, S -> skip_attributes.contains(S)); Assert.assertTrue(codec.canDecode(inputGtf.toAbsolutePath().toString())); - AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( - inputGtf.toAbsolutePath().toString(), null,codec, false); + AbstractFeatureReader reader = AbstractFeatureReader + .getFeatureReader(inputGtf.toAbsolutePath().toString(), null, codec, false); int countTotalFeatures = 0; for (final Gff3Feature feature : reader.iterator()) { - for(final String key : skip_attributes) { + for (final String key : skip_attributes) { Assert.assertTrue(feature.getAttribute(key).isEmpty()); Assert.assertFalse(feature.hasAttribute(key)); Assert.assertFalse(feature.getUniqueAttribute(key).isPresent()); } - countTotalFeatures++; - } + countTotalFeatures++; + } Assert.assertEquals(countTotalFeatures, expectedTotalFeatures); } @Test public void FeatureContentTest() throws IOException { - final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW); - try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(gencode47_PCSK9.toAbsolutePath().toString(), null,codec, false)) { - try(CloseableIterator iter = reader.iterator()) { + final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.SHALLOW); + try (final AbstractFeatureReader reader = + AbstractFeatureReader.getFeatureReader(gencode47_PCSK9.toAbsolutePath().toString(), + null, codec, false)) { + try (CloseableIterator iter = reader.iterator()) { Assert.assertTrue(iter.hasNext()); Gff3Feature feature = iter.next(); Assert.assertNotNull(feature); - Assert.assertEquals(feature.getContig(),"chr1"); - Assert.assertEquals(feature.getSource(),"HAVANA"); - Assert.assertEquals(feature.getType(),"UTR"); - Assert.assertEquals(feature.getStart(),55039445); - Assert.assertEquals(feature.getEnd(),55039837); - Assert.assertEquals(feature.getScore(),-1); - Assert.assertEquals(feature.getStrand(),Strand.POSITIVE); - Assert.assertEquals(feature.getPhase(),-1); - Assert.assertEquals(feature.getUniqueAttribute(GtfConstants.GENE_ID).get(),"ENSG00000169174.13"); - Assert.assertEquals(feature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get(),"ENST00000713785.1"); - Assert.assertEquals(feature.getUniqueAttribute("gene_type").get(),"protein_coding"); - Assert.assertEquals(feature.getUniqueAttribute("gene_name").get(),"PCSK9"); - Assert.assertEquals(feature.getUniqueAttribute("transcript_type").get(),"nonsense_mediated_decay"); - Assert.assertEquals(feature.getUniqueAttribute("transcript_name").get(),"PCSK9-208"); - Assert.assertEquals(feature.getUniqueAttribute("exon_number").get(),"1"); - Assert.assertEquals(feature.getUniqueAttribute("exon_id").get(),"ENSE00004011055.2"); - Assert.assertEquals(feature.getUniqueAttribute("level").get(),"2"); - Assert.assertEquals(feature.getUniqueAttribute("protein_id").get(),"ENSP00000519087.1"); - Assert.assertEquals(feature.getUniqueAttribute("hgnc_id").get(),"HGNC:20001"); - Assert.assertEquals(feature.getUniqueAttribute("havana_gene").get(),"OTTHUMG00000008136.2"); + Assert.assertEquals(feature.getContig(), "chr1"); + Assert.assertEquals(feature.getSource(), "HAVANA"); + Assert.assertEquals(feature.getType(), "UTR"); + Assert.assertEquals(feature.getStart(), 55039445); + Assert.assertEquals(feature.getEnd(), 55039837); + Assert.assertEquals(feature.getScore(), -1); + Assert.assertEquals(feature.getStrand(), Strand.POSITIVE); + Assert.assertEquals(feature.getPhase(), -1); + Assert.assertEquals(feature.getUniqueAttribute(GtfConstants.GENE_ID).get(), + "ENSG00000169174.13"); + Assert.assertEquals(feature.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get(), + "ENST00000713785.1"); + Assert.assertEquals(feature.getUniqueAttribute("gene_type").get(), + "protein_coding"); + Assert.assertEquals(feature.getUniqueAttribute("gene_name").get(), "PCSK9"); + Assert.assertEquals(feature.getUniqueAttribute("transcript_type").get(), + "nonsense_mediated_decay"); + Assert.assertEquals(feature.getUniqueAttribute("transcript_name").get(), + "PCSK9-208"); + Assert.assertEquals(feature.getUniqueAttribute("exon_number").get(), "1"); + Assert.assertEquals(feature.getUniqueAttribute("exon_id").get(), + "ENSE00004011055.2"); + Assert.assertEquals(feature.getUniqueAttribute("level").get(), "2"); + Assert.assertEquals(feature.getUniqueAttribute("protein_id").get(), + "ENSP00000519087.1"); + Assert.assertEquals(feature.getUniqueAttribute("hgnc_id").get(), "HGNC:20001"); + Assert.assertEquals(feature.getUniqueAttribute("havana_gene").get(), + "OTTHUMG00000008136.2"); Assert.assertFalse(feature.getUniqueAttribute("zz").isPresent()); Assert.assertTrue(iter.hasNext()); - } + } } } @Test(dataProvider = "basicDecodeDataProvider") private void deepTreeTest(final Path path, int expectNumber) throws IOException { - final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.DEEP); - try(final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader(path.toAbsolutePath().toString(), null,codec, false)) { - try(CloseableIterator iter = reader.iterator()) { - Assert.assertTrue(iter.hasNext()); - while(iter.hasNext()) { - Gff3Feature f1 = iter.next(); - if(!f1.getType().equals("gene")) continue; - Assert.assertEquals(f1.getType(), "gene"); - Assert.assertTrue(f1.hasAttribute(GtfConstants.GENE_ID)); - Assert.assertTrue(f1.hasAttribute("gene_name")); - Assert.assertTrue(f1.hasChildren()); - for(Gff3Feature f2 : f1.getChildren()) { - Assert.assertEquals(f2.getType(), "transcript"); - Assert.assertTrue(f2.hasAttribute(GtfConstants.GENE_ID)); - Assert.assertTrue(f2.hasAttribute(GtfConstants.TRANSCRIPT_ID)); - Assert.assertTrue(f2.hasAttribute("gene_name")); - Assert.assertEquals(f1.getUniqueAttribute("gene_name").get(),f2.getUniqueAttribute("gene_name").get()); - Assert.assertEquals(f1.getUniqueAttribute(GtfConstants.GENE_ID).get(),f2.getUniqueAttribute(GtfConstants.GENE_ID).get()); - Assert.assertTrue(f2.hasChildren()); - - for(Gff3Feature f3 : f2.getChildren()) { - Assert.assertNotEquals(f3.getType(), "gene"); - Assert.assertNotEquals(f3.getType(), "transcript"); - Assert.assertTrue(f3.hasAttribute(GtfConstants.GENE_ID)); - Assert.assertTrue(f3.hasAttribute(GtfConstants.TRANSCRIPT_ID)); - Assert.assertEquals(f2.getUniqueAttribute(GtfConstants.GENE_ID).get(), f3.getUniqueAttribute(GtfConstants.GENE_ID).get()); - Assert.assertEquals(f2.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get(), f3.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get()); - Assert.assertFalse(f3.hasChildren()); - } - } - - } - } + final GtfCodec codec = new GtfCodec(GtfCodec.DecodeDepth.DEEP); + try (final AbstractFeatureReader reader = AbstractFeatureReader + .getFeatureReader(path.toAbsolutePath().toString(), null, codec, false)) { + try (CloseableIterator iter = reader.iterator()) { + Assert.assertTrue(iter.hasNext()); + while (iter.hasNext()) { + Gff3Feature f1 = iter.next(); + if (!f1.getType().equals("gene")) + continue; + Assert.assertEquals(f1.getType(), "gene"); + Assert.assertTrue(f1.hasAttribute(GtfConstants.GENE_ID)); + Assert.assertTrue(f1.hasAttribute("gene_name")); + Assert.assertTrue(f1.hasChildren()); + for (Gff3Feature f2 : f1.getChildren()) { + Assert.assertEquals(f2.getType(), "transcript"); + Assert.assertTrue(f2.hasAttribute(GtfConstants.GENE_ID)); + Assert.assertTrue(f2.hasAttribute(GtfConstants.TRANSCRIPT_ID)); + + Assert.assertTrue(f2.hasAttribute("gene_name")); + Assert.assertEquals(f1.getUniqueAttribute("gene_name").get(), + f2.getUniqueAttribute("gene_name").get()); + Assert.assertEquals(f1.getUniqueAttribute(GtfConstants.GENE_ID).get(), + f2.getUniqueAttribute(GtfConstants.GENE_ID).get()); + Assert.assertTrue(f2.hasChildren()); + + for (Gff3Feature f3 : f2.getChildren()) { + Assert.assertNotEquals(f3.getType(), "gene"); + Assert.assertNotEquals(f3.getType(), "transcript"); + Assert.assertTrue(f3.hasAttribute(GtfConstants.GENE_ID)); + Assert.assertTrue(f3.hasAttribute(GtfConstants.TRANSCRIPT_ID)); + Assert.assertEquals(f2.getUniqueAttribute(GtfConstants.GENE_ID).get(), + f3.getUniqueAttribute(GtfConstants.GENE_ID).get()); + Assert.assertEquals( + f2.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get(), + f3.getUniqueAttribute(GtfConstants.TRANSCRIPT_ID).get()); + Assert.assertFalse(f3.hasChildren()); + } + } + + } + } } } - + @Test public void decodeFeaturesTest() throws IOException { - String s = GtfConstants.UNDEFINED_FIELD_VALUE; - Map> h = GtfCodec.parseAttributes(s); + String s = GtfConstants.UNDEFINED_FIELD_VALUE; + Map> h = GtfCodec.parseAttributes(s); Assert.assertTrue(h.isEmpty()); - + h = GtfCodec.parseAttributes("key1 \"ABCD\"; key2 12345 ; key3 'hello'"); - for(String k: h.keySet()) { - Assert.assertEquals(h.get(k).size(),1); - } - Assert.assertEquals(h.get("key1").get(0),"ABCD"); - Assert.assertEquals(h.get("key2").get(0),"12345"); - Assert.assertEquals(h.get("key3").get(0),"hello"); + for (String k : h.keySet()) { + Assert.assertEquals(h.get(k).size(), 1); + } + Assert.assertEquals(h.get("key1").get(0), "ABCD"); + Assert.assertEquals(h.get("key2").get(0), "12345"); + Assert.assertEquals(h.get("key3").get(0), "hello"); + + h = GtfCodec.parseAttributes("key1 \"A\"; key1 B ; key1 C"); + Assert.assertEquals(h.size(), 1); + Assert.assertEquals(h.get("key1").size(), 3); + Assert.assertEquals(h.get("key1"), Arrays.asList("A", "B", "C")); + + h = GtfCodec.parseAttributes("key1 \"A\\tB\""); + Assert.assertEquals(h.get("key1").get(0), "A\tB"); } - } diff --git a/src/test/java/htsjdk/tribble/gff/GtfWriterTest.java b/src/test/java/htsjdk/tribble/gff/GtfWriterTest.java index 4b783a546d..2884fafa9c 100644 --- a/src/test/java/htsjdk/tribble/gff/GtfWriterTest.java +++ b/src/test/java/htsjdk/tribble/gff/GtfWriterTest.java @@ -30,31 +30,27 @@ public class GtfWriterTest extends HtsjdkTest { @DataProvider(name = "roundTripDataProvider") public Object[][] roundTripDataProvider() { - return new Object[][] { - {gencode47_gzipped}, - {gencode47_PCSK9} - }; + return new Object[][] {{gencode47_gzipped}, {gencode47_PCSK9}}; } - + @Test(dataProvider = "roundTripDataProvider") public void testRoundTrip(final Path path) { final List comments1 = new ArrayList<>(); final LinkedHashSet features1 = readFromFile(path, comments1); - //write out to temp files (one gzipped, on not) + // write out to temp files (one gzipped, on not) try { - int n; - IOUtil.newTempPath("gtfWriter", ".gtf", tmpDir);// REMOVE ME - final Path tempFile = Paths.get(path.toString()+".tmp.gtf");// IOUtil.newTempPath("gtfWriter", ".gtf", tmpDir); - final Path tempFileGzip = Paths.get(path.toString()+".tmp.gtf.gz");//IOUtil.newTempPath("gtfWriter", ".gtf.gz", tmpDir); + int n; + final Path tempFile = IOUtil.newTempPath("gtfWriter",".gtf", tmpDir); + final Path tempFileGzip = IOUtil.newTempPath("gtfWriter",".gtf.gz", tmpDir); n = writeToFile(tempFile, comments1, features1); Assert.assertEquals(n, features1.size()); n = writeToFile(tempFileGzip, comments1, features1); Assert.assertEquals(n, features1.size()); - //read temp files back in + // read temp files back in Assert.assertTrue(isGZipped(tempFileGzip.toFile())); final List comments2 = new ArrayList<>(); @@ -76,10 +72,11 @@ public void testRoundTrip(final Path path) { } } - private int writeToFile(final Path path, final List comments, final Set features) { - int n = 0; - try (final AbstractGxxWriter writer =AbstractGxxWriter.openWithFileExtension(path)) { - Assert.assertTrue(writer instanceof GtfWriter); + private int writeToFile(final Path path, final List comments, + final Set features) { + int n = 0; + try (final AbstractGxxWriter writer = AbstractGxxWriter.openWithFileExtension(path)) { + Assert.assertTrue(writer instanceof GtfWriter); for (final String comment : comments) { writer.addComment(comment); } @@ -97,8 +94,8 @@ private int writeToFile(final Path path, final List comments, final Set< private LinkedHashSet readFromFile(final Path path, List commentsStore) { final GtfCodec codec = new GtfCodec(); final LinkedHashSet features = new LinkedHashSet<>(); - try (final AbstractFeatureReader reader = AbstractFeatureReader.getFeatureReader( - path.toAbsolutePath().toString(), null, codec, false)) { + try (final AbstractFeatureReader reader = AbstractFeatureReader + .getFeatureReader(path.toAbsolutePath().toString(), null, codec, false)) { for (final Gff3Feature feature : reader.iterator()) { features.add(feature); } @@ -107,20 +104,16 @@ private LinkedHashSet readFromFile(final Path path, List co } catch (final IOException ex) { throw new TribbleException("Error reading gtf file " + path); } - System.err.println("###ICIB "+features.size()); + System.err.println("###ICIB " + features.size()); return features; } @DataProvider(name = "encodeStringDataProvider") public Object[][] encodeStringDataProvider() { - return new Object[][] { - {"%", "%25"}, - {";", "%3B"}, - {"=", "%3D"}, - {"&", "%26"}, - {",", "%2C"}, - {" ", " "}, - {"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ", "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM "} //these should remain unchanged + return new Object[][] {{"%", "%25"}, {";", "%3B"}, {"=", "%3D"}, {"&", "%26"}, {",", "%2C"}, + {" ", " "}, {"qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ", + "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM "} // these should + // remain unchanged }; } From bf631c10d380dc32e37bdc38a57ed15a8aa72dc6 Mon Sep 17 00:00:00 2001 From: Pierre Lindenbaum Date: Tue, 15 Apr 2025 13:27:37 +0200 Subject: [PATCH 7/7] double quote gtfwriter --- src/main/java/htsjdk/tribble/gff/GtfWriter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/htsjdk/tribble/gff/GtfWriter.java b/src/main/java/htsjdk/tribble/gff/GtfWriter.java index 6ae041c17b..31a0899ca9 100644 --- a/src/main/java/htsjdk/tribble/gff/GtfWriter.java +++ b/src/main/java/htsjdk/tribble/gff/GtfWriter.java @@ -37,7 +37,9 @@ protected void writeAttributes(final Map> attributes) throw out.write(GtfConstants.ATTRIBUTE_DELIMITER); out.write(escapeString(key).getBytes()); out.write(GtfConstants.VALUE_DELIMITER); + out.write('\"'); out.write(escapeString(value).getBytes()); + out.write('\"'); first = false; } }