From 71727e82c8b14864f2e91c76150b6072b0ea3cc3 Mon Sep 17 00:00:00 2001 From: lindenb Date: Tue, 29 Oct 2024 12:17:26 +0100 Subject: [PATCH 1/3] improve gff --- .../java/htsjdk/tribble/gff/Gff3BaseData.java | 42 ++++++++++++++++++- .../java/htsjdk/tribble/gff/Gff3Feature.java | 25 +++++++++++ .../htsjdk/tribble/gff/Gff3CodecTest.java | 6 +++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java index 0bb414f917..9692dd66c9 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java @@ -1,5 +1,6 @@ package htsjdk.tribble.gff; +import htsjdk.samtools.util.Locatable; import htsjdk.tribble.annotation.Strand; import java.util.ArrayList; @@ -7,8 +8,9 @@ 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; @@ -116,6 +118,7 @@ private int computeHashCode() { return hash; } + @Override public String getContig() { return contig; } @@ -128,10 +131,12 @@ public String getType() { return type; } + @Override public int getStart() { return start; } + @Override public int getEnd() { return end; } @@ -152,10 +157,45 @@ public Map> getAttributes() { return attributes; } + /** + * 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 + */ public List getAttribute(final String key) { return attributes.getOrDefault(key, Collections.emptyList()); } + /** + * Returns true if this record contains an attribute for the specified key. + * + * @param key key whose presence in this map is to be tested + * @return true 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. : 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 + * @throws IllegalArgumentException if there is more than one value + */ + public Optional getAttr(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("getAttr cannot be called with key="+key+" because it contains more than one value " + String.join(", ", atts)); + } + } + public String getId() { return id; } diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Feature.java b/src/main/java/htsjdk/tribble/gff/Gff3Feature.java index 53ac1ea92e..005f0b73c5 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Feature.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Feature.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -55,7 +56,31 @@ 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. + * + * @param key key whose presence in this map is to be tested + * @return true 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. : 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 + * @throws IllegalArgumentException if there is more than one value. + */ + default Optional getAttr(final String key) { + return getBaseData().getAttr(key); + } + default Map> getAttributes() { return getBaseData().getAttributes();} default String getID() { return getBaseData().getId();} diff --git a/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java b/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java index d475f125fb..7a59cddd1f 100644 --- a/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java +++ b/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java @@ -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.getAttr(key).isPresent()); } countTotalFeatures++; } @@ -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.getAttr(Gff3Constants.ID_ATTRIBUTE_KEY).isPresent()); + Assert.assertFalse(feature.getAttr("missing").isPresent()); } From 190dc4d3fea527d027ba8e29af2dee465b6e102d Mon Sep 17 00:00:00 2001 From: lindenb Date: Tue, 29 Oct 2024 16:17:38 +0100 Subject: [PATCH 2/3] answer to https://github.com/samtools/htsjdk/pull/1726#issuecomment-2444507432 --- src/main/java/htsjdk/tribble/gff/Gff3BaseData.java | 2 +- src/main/java/htsjdk/tribble/gff/Gff3Feature.java | 4 ++-- src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java index 9692dd66c9..c1f746d261 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java @@ -187,7 +187,7 @@ public boolean hasAttribute(final String 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 getAttr(final String key) { + public Optional getUniqueAttribute(final String key) { final List atts = getAttribute(key); switch(atts.size()) { case 0 : return Optional.empty(); diff --git a/src/main/java/htsjdk/tribble/gff/Gff3Feature.java b/src/main/java/htsjdk/tribble/gff/Gff3Feature.java index 005f0b73c5..37a879a5b9 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3Feature.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3Feature.java @@ -77,8 +77,8 @@ default boolean hasAttribute(final String 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 getAttr(final String key) { - return getBaseData().getAttr(key); + default Optional getUniqueAttribute(final String key) { + return getBaseData().getUniqueAttribute(key); } default Map> getAttributes() { return getBaseData().getAttributes();} diff --git a/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java b/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java index 7a59cddd1f..38b9fb5d9c 100644 --- a/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java +++ b/src/test/java/htsjdk/tribble/gff/Gff3CodecTest.java @@ -80,7 +80,7 @@ public void codecFilterOutFieldsTest(final Path inputGff3, final int expectedTot for(final String key : skip_attributes) { Assert.assertTrue(feature.getAttribute(key).isEmpty()); Assert.assertFalse(feature.hasAttribute(key)); - Assert.assertFalse(feature.getAttr(key).isPresent()); + Assert.assertFalse(feature.getUniqueAttribute(key).isPresent()); } countTotalFeatures++; } @@ -203,8 +203,8 @@ public void urlDecodingTest() throws IOException { 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.getAttr(Gff3Constants.ID_ATTRIBUTE_KEY).isPresent()); - Assert.assertFalse(feature.getAttr("missing").isPresent()); + Assert.assertTrue(feature.getUniqueAttribute(Gff3Constants.ID_ATTRIBUTE_KEY).isPresent()); + Assert.assertFalse(feature.getUniqueAttribute("missing").isPresent()); } From 280e49a3f217d6f02a299eabc2aedd5de4b3ae7b Mon Sep 17 00:00:00 2001 From: lindenb Date: Tue, 29 Oct 2024 17:02:59 +0100 Subject: [PATCH 3/3] fix message --- src/main/java/htsjdk/tribble/gff/Gff3BaseData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java index c1f746d261..8d35978860 100644 --- a/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java +++ b/src/main/java/htsjdk/tribble/gff/Gff3BaseData.java @@ -192,7 +192,7 @@ public Optional getUniqueAttribute(final String key) { switch(atts.size()) { case 0 : return Optional.empty(); case 1 : return Optional.of(atts.get(0)); - default : throw new IllegalArgumentException("getAttr cannot be called with key="+key+" because it contains more than one value " + String.join(", ", atts)); + default : throw new IllegalArgumentException("getUniqueAttribute cannot be called with key="+key+" because it contains more than one value " + String.join(", ", atts)); } }