Skip to content

Commit

Permalink
Improve GFF API by adding new convenience methods
Browse files Browse the repository at this point in the history
* new methods getUniqueAttribute() and hasAttribute() for common GFF use scenarios
* make Gff3BaseData Locatable
  • Loading branch information
lindenb authored Oct 29, 2024
1 parent 4ea3285 commit dbebac3
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 1 deletion.
42 changes: 41 additions & 1 deletion src/main/java/htsjdk/tribble/gff/Gff3BaseData.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package htsjdk.tribble.gff;

import htsjdk.samtools.util.Locatable;
import htsjdk.tribble.annotation.Strand;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class Gff3BaseData {
public class Gff3BaseData implements Locatable {
private final String contig;
private final String source;
private final String type;
Expand Down Expand Up @@ -116,6 +118,7 @@ private int computeHashCode() {
return hash;
}

@Override
public String getContig() {
return contig;
}
Expand All @@ -128,10 +131,12 @@ public String getType() {
return type;
}

@Override
public int getStart() {
return start;
}

@Override
public int getEnd() {
return end;
}
Expand All @@ -152,10 +157,45 @@ public Map<String, List<String>> getAttributes() {
return attributes;
}

/**
* get the values as List for the <tt>key</tt>, or an empty list if this <tt>key</tt> 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
*/
public List<String> getAttribute(final String key) {
return attributes.getOrDefault(key, Collections.emptyList());
}

/**
* Returns <tt>true</tt> if this record contains an attribute for the specified key.
*
* @param key key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains an attribute for the specified 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. : <tt>gene_biotype</tt>, <tt>gene_name</tt>, etc ...
* This function returns an <tt>Optional.empty</tt> if the <tt>key</tt> is not present,
* an <tt>Optional.of(value)</tt> if there is only one value associated to the <tt>key</tt>,
* or it throws an <tt>IllegalArgumentException</tt> if there is more than one value.
*
* @param key key whose presence in the attributes is to be tested
* @return <tt>Optional&lt;String&gt;</tt> if this map contains zero or one attribute for the specified key
* @throws IllegalArgumentException if there is more than one value
*/
public Optional<String> getUniqueAttribute(final String key) {
final List<String> 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));
}
}

public String getId() {
return id;
}
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/htsjdk/tribble/gff/Gff3Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
Expand Down Expand Up @@ -55,7 +56,31 @@ default int getStart() {
default List<String> getAttribute(final String key) {
return getBaseData().getAttribute(key);
}

/**
* Returns <tt>true</tt> if this record contains an attribute for the specified key.
*
* @param key key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains an attribute for the specified key
*/
default boolean hasAttribute(final String key) {
return getBaseData().hasAttribute(key);
}

/**
* Most attributes in a GFF file are present just one time in a line, e.g. : <tt>gene_biotype</tt>, <tt>gene_name</tt>, etc ...
* This function returns an <tt>Optional.empty</tt> if the <tt>key</tt> is not present,
* an <tt>Optional.of(value)</tt> if there is only one value associated to the <tt>key</tt>,
* or it throws an <tt>IllegalArgumentException</tt> if there is more than one value.
*
* @param key key whose presence in the attributes is to be tested
* @return <tt>Optional&lt;String&gt;</tt> if this map contains zero or one attribute for the specified key
* @throws IllegalArgumentException if there is more than one value.
*/
default Optional<String> getUniqueAttribute(final String key) {
return getBaseData().getUniqueAttribute(key);
}

default Map<String, List<String>> getAttributes() { return getBaseData().getAttributes();}

default String getID() { return getBaseData().getId();}
Expand Down
6 changes: 6 additions & 0 deletions src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public void codecFilterOutFieldsTest(final Path inputGff3, final int expectedTot
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++;
}
Expand Down Expand Up @@ -199,6 +201,10 @@ public void urlDecodingTest() throws IOException {
Assert.assertEquals(feature.getType(), "a region");
Assert.assertEquals(feature.getID(), "this is the ID of this wacky feature^&%##$%*&>,. ,.");
Assert.assertEquals(feature.getAttribute("Another key"), Arrays.asList("Another=value", "And a second, value"));
Assert.assertTrue(feature.hasAttribute("Another key"));
Assert.assertTrue(feature.hasAttribute(Gff3Constants.ID_ATTRIBUTE_KEY));
Assert.assertTrue(feature.getUniqueAttribute(Gff3Constants.ID_ATTRIBUTE_KEY).isPresent());
Assert.assertFalse(feature.getUniqueAttribute("missing").isPresent());
}


Expand Down

0 comments on commit dbebac3

Please sign in to comment.