Skip to content

Commit

Permalink
Add Gff3 Writer (#1486)
Browse files Browse the repository at this point in the history
* Adds a Gff3Writer to write out Gff3 files. 
* Also adjusts the attributes field to account for the fact that attributes map a key to a list of strings, which are separated by commas. As previously written, the comma separated list was left as a single string, which could later be processed into a list by the user. This was vulnerable to situations where one of the entries in the list contained an encoded comma (ie %2C), so that after decoding the decoded comma could not be differentiated from the list separating commas.
* This required breaking changes to the existing Gff3 api's but the changes should be simple for downstream users to adapt.
  • Loading branch information
kachulis authored Jul 8, 2020
1 parent a98d3a7 commit 99548ce
Show file tree
Hide file tree
Showing 12 changed files with 785 additions and 166 deletions.
47 changes: 29 additions & 18 deletions src/main/java/htsjdk/tribble/gff/Gff3BaseData.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

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;

public class Gff3BaseData {
Expand All @@ -18,15 +20,15 @@ public class Gff3BaseData {
private final double score;
private final Strand strand;
private final int phase;
private final Map<String, String> attributes;
private final Map<String, List<String>> attributes;
private final String id;
private final String name;
private final String alias;
private final List<String> aliases;
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<String, String> attributes) {
final Map<String, List<String>> attributes) {
this.contig = contig;
this.source = source;
this.type = type;
Expand All @@ -35,13 +37,24 @@ public Gff3BaseData(final String contig, final String source, final String type,
this.score = score;
this.phase = phase;
this.strand = strand;
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
this.id = attributes.get(ID_ATTRIBUTE_KEY);
this.name = attributes.get(NAME_ATTRIBUTE_KEY);
this.alias = attributes.get(ALIAS_ATTRIBUTE_KEY);
this.attributes = copyAttributesSafely(attributes);
this.id = Gff3Codec.extractSingleAttribute(attributes.get(ID_ATTRIBUTE_KEY));
this.name = Gff3Codec.extractSingleAttribute(attributes.get(NAME_ATTRIBUTE_KEY));
this.aliases = attributes.getOrDefault(ALIAS_ATTRIBUTE_KEY, Collections.emptyList());
this.hashCode = computeHashCode();
}

private static Map<String, List<String>> copyAttributesSafely(final Map<String, List<String>> attributes) {
final Map<String, List<String>> modifiableDeepMap = new LinkedHashMap<>();

for (final Map.Entry<String, List<String>> entry : attributes.entrySet()) {
final List<String> unmodifiableDeepList = Collections.unmodifiableList(new ArrayList<>(entry.getValue()));
modifiableDeepMap.put(entry.getKey(), unmodifiableDeepList);
}

return Collections.unmodifiableMap(modifiableDeepMap);
}

@Override
public boolean equals(Object other) {
if (other == this) {
Expand Down Expand Up @@ -73,11 +86,7 @@ public boolean equals(Object other) {
ret = ret && otherBaseData.getName() != null && otherBaseData.getName().equals(getName());
}

if (getAlias() == null) {
ret = ret && otherBaseData.getAlias() == null;
} else {
ret = ret && otherBaseData.getAlias() != null && otherBaseData.getAlias().equals(getAlias());
}
ret = ret && otherBaseData.getAliases().equals(getAliases());

return ret;
}
Expand Down Expand Up @@ -105,9 +114,7 @@ private int computeHashCode() {
hash = 31 * hash + getName().hashCode();
}

if (getAlias() != null) {
hash = 31 * hash + getAlias().hashCode();
}
hash = 31 * hash + aliases.hashCode();

return hash;
}
Expand Down Expand Up @@ -144,10 +151,14 @@ public int getPhase() {
return phase;
}

public Map<String, String> getAttributes() {
public Map<String, List<String>> getAttributes() {
return attributes;
}

public List<String> getAttribute(final String key) {
return attributes.getOrDefault(key, Collections.emptyList());
}

public String getId() {
return id;
}
Expand All @@ -156,7 +167,7 @@ public String getName() {
return name;
}

public String getAlias() {
return alias;
public List<String> getAliases() {
return aliases;
}
}
Loading

0 comments on commit 99548ce

Please sign in to comment.