Skip to content

Commit 29c854f

Browse files
committed
Versioned header line validation framework.
1 parent f9a0c08 commit 29c854f

15 files changed

+92
-86
lines changed

src/main/java/htsjdk/variant/vcf/VCFAltHeaderLine.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public VCFAltHeaderLine(final String line, final VCFHeaderVersion version) {
2222
// Honor the requested version to choose the parser, and let validateForVersion figure out
2323
// whether that version is valid for this line (for example, if this is called with a pre-4.0 version)
2424
super(VCFConstants.ALT_HEADER_KEY, VCFHeaderLineTranslator.parseLine(version, line, expectedTags));
25-
validateForVersion(version);
25+
validateForVersionOrThrow(version);
2626
}
2727

2828
public VCFAltHeaderLine(final String id, final String description) {
@@ -35,7 +35,7 @@ public VCFAltHeaderLine(final String id, final String description) {
3535
}
3636

3737
@Override
38-
public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final VCFHeaderVersion vcfTargetVersion) {
38+
public Optional<VCFValidationFailure<VCFHeaderLine>> validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
3939
//TODO: Should we validate/constrain these to match the 4.3 spec constraints ?
4040
if (!vcfTargetVersion.isAtLeastAsRecentAs(VCFHeaderVersion.VCF4_0)) {
4141
final VCFValidationFailure<VCFHeaderLine> validationFailure = new VCFValidationFailure<>(
@@ -49,6 +49,6 @@ public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final
4949
}
5050
}
5151

52-
return super.getValidationFailure(vcfTargetVersion);
52+
return super.validateForVersion(vcfTargetVersion);
5353
}
5454
}

src/main/java/htsjdk/variant/vcf/VCFCompoundHeaderLine.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ protected VCFCompoundHeaderLine(final String key, final Map<String, String> mapp
126126
final String countString = getGenericFieldValue(NUMBER_ATTRIBUTE);
127127
this.countType = decodeCountType(countString, vcfVersion);
128128
this.count = decodeCount(countString, this.countType);
129-
validateForVersion(vcfVersion);
129+
validateForVersionOrThrow(vcfVersion);
130130
}
131131

132132
/**
@@ -174,7 +174,7 @@ public String getVersion() {
174174
}
175175

176176
@Override
177-
public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final VCFHeaderVersion vcfTargetVersion) {
177+
public Optional<VCFValidationFailure<VCFHeaderLine>> validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
178178
// The VCF 4.3 spec does not phrase this restriction as one on the form of the ID value of
179179
// INFO/FORMAT lines but instead on the INFO/FORMAT fixed field key values (c.f. section 1.6.1).
180180
// However, the key values correspond to INFO/FORMAT header lines defining the attribute and its type,
@@ -194,7 +194,7 @@ public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final
194194
}
195195
}
196196

197-
return super.getValidationFailure(vcfTargetVersion);
197+
return super.validateForVersion(vcfTargetVersion);
198198
}
199199

200200
/**

src/main/java/htsjdk/variant/vcf/VCFContigHeaderLine.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public VCFContigHeaderLine(final String line, final VCFHeaderVersion version, fi
9797
if (contigIndex < 0) {
9898
throw new TribbleException(String.format("The contig index (%d) is less than zero.", contigIndex));
9999
}
100-
validateForVersion(version);
100+
validateForVersionOrThrow(version);
101101
}
102102

103103
public VCFContigHeaderLine(final Map<String, String> mapping, final int contigIndex) {
@@ -186,7 +186,7 @@ public SAMSequenceRecord getSAMSequenceRecord() {
186186
}
187187

188188
@Override
189-
public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final VCFHeaderVersion vcfTargetVersion) {
189+
public Optional<VCFValidationFailure<VCFHeaderLine>> validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
190190
if (vcfTargetVersion.isAtLeastAsRecentAs(VCFHeaderVersion.VCF4_3)) {
191191
if (!VALID_CONTIG_ID_PATTERN.matcher(getID()).matches()) {
192192
return Optional.of(new VCFValidationFailure<>(
@@ -196,7 +196,7 @@ public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final
196196
}
197197
}
198198

199-
return super.getValidationFailure(vcfTargetVersion);
199+
return super.validateForVersion(vcfTargetVersion);
200200
}
201201

202202
public Integer getContigIndex() {

src/main/java/htsjdk/variant/vcf/VCFFilterHeaderLine.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public VCFFilterHeaderLine(final String name) {
7878
public VCFFilterHeaderLine(final String line, final VCFHeaderVersion version) {
7979
super(VCFConstants.FILTER_HEADER_KEY, VCFHeaderLineTranslator.parseLine(version, line, requiredTagOrder));
8080
validate();
81-
validateForVersion(version);
81+
validateForVersionOrThrow(version);
8282
}
8383

8484
private void validate() {

src/main/java/htsjdk/variant/vcf/VCFFormatHeaderLine.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public VCFFormatHeaderLine(String line, VCFHeaderVersion version) {
5757
VCFHeaderLineTranslator.parseLine(version, line, expectedTagOrder),
5858
version);
5959
validate();
60-
validateForVersion(version);
60+
validateForVersionOrThrow(version);
6161
}
6262

6363
/**

src/main/java/htsjdk/variant/vcf/VCFHeader.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public VCFHeader(final Set<VCFHeaderLine> metaData, final List<String> genotypeS
161161
// and the header version will be established using the last version line encountered
162162
mMetaData.addMetaDataLines(metaData);
163163
final VCFHeaderVersion vcfHeaderVersion = initializeHeaderVersion();
164-
mMetaData.validateMetaDataLines(vcfHeaderVersion);
164+
mMetaData.validateMetaDataLinesOrThrow(vcfHeaderVersion);
165165

166166
checkForDeprecatedGenotypeLikelihoodsKey();
167167
if ( genotypeSampleNames.size() != new HashSet<>(genotypeSampleNames).size() )
@@ -625,9 +625,9 @@ private void validateVersionTransition(
625625

626626
// the version moved forward, so validate ALL of the existing lines in the list to ensure
627627
// that the transition is valid
628-
mMetaData.validateMetaDataLines(newVersion);
628+
mMetaData.validateMetaDataLinesOrThrow(newVersion);
629629
} else {
630-
newHeaderLine.validateForVersion(newVersion);
630+
newHeaderLine.validateForVersionOrThrow(newVersion);
631631
}
632632
}
633633

src/main/java/htsjdk/variant/vcf/VCFHeaderLine.java

+44-34
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,13 @@ public class VCFHeaderLine implements Comparable, Serializable {
5050
* @param key the key for this header line
5151
* @param value the value for this header line
5252
*/
53-
public VCFHeaderLine(String key, String value) {
53+
public VCFHeaderLine(final String key, final String value) {
54+
final Optional<String> validationFailure = validateAttributeName(key, "header line key");
55+
if (validationFailure.isPresent()) {
56+
throw new TribbleException(validationFailure.get());
57+
}
5458
mKey = key;
5559
mValue = value;
56-
validate();
5760
}
5861

5962
/**
@@ -86,15 +89,20 @@ public String getValue() {
8689
public String getID() { return null; }
8790

8891
/**
89-
* Validates this header line against {@code vcfTargetVersion}.
90-
* Subclasses can override this to provide line type-specific version validation, and the
91-
* overrides should also call super.getValidationFailure to allow each class in the class hierarchy
92-
* to do class-level validation.
92+
* Validates this header line against {@code vcfTargetVersion} and returns a {@link VCFValidationFailure}
93+
* describing the reaon for the failure, if one exists. This method is used to report the reason for a
94+
* version upgrade failure.
95+
*
96+
* Subclasses can override this to provide line type-specific version validation. Overrides should
97+
* call super.validateForVersion to allow each class in the hierarchy to do class-level validation.
9398
*
99+
* @param vcfTargetVersion
94100
* @return Optional containing a {@link VCFValidationFailure} describing validation failure if this
95101
* line fails validation, otherwise Optional.empty().
96102
*/
97-
public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final VCFHeaderVersion vcfTargetVersion) {
103+
public Optional<VCFValidationFailure<VCFHeaderLine>> validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
104+
ValidationUtils.nonNull(vcfTargetVersion);
105+
98106
// If this header line is itself a fileformat/version line,
99107
// make sure it doesn't clash with the requested vcfTargetVersion.
100108
if (VCFHeaderVersion.isFormatString(getKey())) {
@@ -124,33 +132,42 @@ public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final
124132
}
125133

126134
/**
127-
* Validate that the header line conforms to {@code vcfTargetVersion.
128-
* @param vcfTargetVersion
135+
* Validate that the header line conforms to {@code vcfTargetVersion. throws if the line fails to
136+
* validate for the target version.
137+
*
138+
* @param vcfTargetVersion the version agint which to validate the line
129139
* @throws {@link TribbleException.VersionValidationFailure} if this header line fails to conform
130140
*/
131-
public void validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
132-
final Optional<VCFValidationFailure<VCFHeaderLine>> error = getValidationFailure(vcfTargetVersion);
133-
if (error.isPresent()) {
134-
throw new TribbleException.VersionValidationFailure(error.get().getSourceMessage());
141+
public void validateForVersionOrThrow(final VCFHeaderVersion vcfTargetVersion) {
142+
ValidationUtils.nonNull(vcfTargetVersion);
143+
final Optional<VCFValidationFailure<VCFHeaderLine>> versionValidationFailure = validateForVersion(vcfTargetVersion);
144+
if (versionValidationFailure.isPresent()) {
145+
throw new TribbleException.VersionValidationFailure(versionValidationFailure.get().getSourceMessage());
135146
}
136147
}
137148

138149
/**
139-
* Validate a string that is to be used as a unique id or key field.
150+
* Validate a string that is to be used as a unique id or key field, and return an Optional String describing
151+
* the validation failure.
152+
*
153+
* @param targetString the string to validate
154+
* @param targetContext the context for which the {@code targetString} is used. Used when reporting validation
155+
* failures. May not be null.
156+
* @return an Optional String containing an error message
140157
*/
141-
protected static void validateKeyOrID(final String keyString, final String sourceName) {
142-
ValidationUtils.nonNull(sourceName);
143-
if (keyString == null) {
144-
throw new TribbleException(
145-
String.format("VCFHeaderLine: %s cannot be null or empty", sourceName));
146-
}
147-
if ( keyString.contains("<") || keyString.contains(">") ) {
148-
throw new TribbleException(
149-
String.format("VCFHeaderLine: %s cannot contain angle brackets", sourceName));
150-
}
151-
if ( keyString.contains("=") ) {
152-
throw new TribbleException(
153-
String.format("VCFHeaderLine: %s cannot contain an equals sign", sourceName));
158+
protected static Optional<String> validateAttributeName(final String targetString, final String targetContext) {
159+
ValidationUtils.nonNull(targetContext);
160+
161+
if (targetString == null) {
162+
return Optional.of(String.format("VCFHeaderLine: %s is null", targetContext));
163+
} else if (targetString.length() < 1) {
164+
return Optional.of(String.format("VCFHeaderLine: %s has zero length", targetContext));
165+
} else if ( targetString.contains("<") || targetString.contains(">") ) {
166+
return Optional.of(String.format("VCFHeaderLine: angle brackets not allowed in \"%s\" value", targetContext));
167+
} else if ( targetString.contains("=") ) {
168+
return Optional.of(String.format("VCFHeaderLine: equals sign not allowed in %s value \"%s\"", targetContext, targetString));
169+
} else {
170+
return Optional.empty();
154171
}
155172
}
156173

@@ -239,13 +256,6 @@ public static String toStringEncoding(Map<String, ? extends Object> keyValues) {
239256
return builder.toString();
240257
}
241258

242-
/**
243-
* Validate the state of this header line. Require the key be valid as an "id".
244-
*/
245-
private void validate() {
246-
validateKeyOrID(mKey, "key");
247-
}
248-
249259
private static String escapeQuotes(final String value) {
250260
// java escaping in a string literal makes this harder to read than it should be
251261
// without string literal escaping and quoting the regex would be: replaceAll( ([^\])" , $1\" )

src/main/java/htsjdk/variant/vcf/VCFInfoHeaderLine.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public VCFInfoHeaderLine(String line, VCFHeaderVersion version) {
6767
VCFHeaderLineTranslator.parseLine(version, line, expectedTagOrder),
6868
version
6969
);
70-
validateForVersion(version);
70+
validateForVersionOrThrow(version);
7171
}
7272

7373
/**

src/main/java/htsjdk/variant/vcf/VCFMetaDataLines.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ public VCFHeaderLine findEquivalentHeaderLine(final VCFHeaderLine queryLine) {
171171
*/
172172
//TODO: we need to tell users how to resolve the case where this fails due to version validation
173173
//i.e, use a custom upgrade tool
174-
public void validateMetaDataLines(final VCFHeaderVersion targetVersion) {
174+
public void validateMetaDataLinesOrThrow(final VCFHeaderVersion targetVersion) {
175175
mMetaData.values().forEach(headerLine -> {
176176
if (!VCFHeaderVersion.isFormatString(headerLine.getKey())) {
177-
headerLine.validateForVersion(targetVersion);
177+
headerLine.validateForVersionOrThrow(targetVersion);
178178
}
179179
});
180180
}
@@ -190,7 +190,7 @@ public void validateMetaDataLines(final VCFHeaderVersion targetVersion) {
190190
public Collection<VCFValidationFailure> getValidationErrors(final VCFHeaderVersion targetVersion) {
191191
return mMetaData.values().stream()
192192
.filter(line -> !VCFHeaderVersion.isFormatString(line.getKey()))
193-
.map(l -> l.getValidationFailure(targetVersion))
193+
.map(l -> l.validateForVersion(targetVersion))
194194
.filter(o -> o.isPresent())
195195
.map(o -> o.get())
196196
.collect(Collectors.toList());

src/main/java/htsjdk/variant/vcf/VCFMetaHeaderLine.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ public VCFMetaHeaderLine(final String line, final VCFHeaderVersion version) {
1515
// other tags. So let validateForVersion detect any version incompatibility, ie., if this is ever
1616
// called with a V3 version.
1717
super(VCFConstants.META_HEADER_KEY, new VCF4Parser().parseLine(line, expectedTagOrder));
18-
validateForVersion(version);
18+
validateForVersionOrThrow(version);
1919
}
2020

2121
public VCFMetaHeaderLine(final Map<String, String> mapping) {
2222
super(VCFConstants.META_HEADER_KEY, mapping);
2323
}
2424

2525
@Override
26-
public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final VCFHeaderVersion vcfTargetVersion) {
26+
public Optional<VCFValidationFailure<VCFHeaderLine>> validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
2727
if (!vcfTargetVersion.isAtLeastAsRecentAs(VCFHeaderVersion.VCF4_3)) {
2828
return Optional.of(
2929
new VCFValidationFailure<>(
@@ -35,7 +35,7 @@ public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final
3535
)));
3636
}
3737

38-
return super.getValidationFailure(vcfTargetVersion);
38+
return super.validateForVersion(vcfTargetVersion);
3939
}
4040

4141
}

src/main/java/htsjdk/variant/vcf/VCFPedigreeHeaderLine.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ public VCFPedigreeHeaderLine(String line, VCFHeaderVersion version) {
2121
// other tags. So let validateForVersion detect any version incompatibility, ie., if this is ever
2222
// called with a V3 version.
2323
super(VCFConstants.PEDIGREE_HEADER_KEY, new VCF4Parser().parseLine(line, expectedTagOrder));
24-
validateForVersion(version);
24+
validateForVersionOrThrow(version);
2525
}
2626

2727
public VCFPedigreeHeaderLine(final Map<String, String> mapping) {
2828
super(VCFConstants.PEDIGREE_HEADER_KEY, mapping);
2929
}
3030

3131
@Override
32-
public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final VCFHeaderVersion vcfTargetVersion) {
32+
public Optional<VCFValidationFailure<VCFHeaderLine>> validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
3333
if (!vcfTargetVersion.isAtLeastAsRecentAs(VCFHeaderVersion.VCF4_3)) {
3434
// previous to VCFv4.3, the PEDIGREE line did not have an ID. Such lines are not modeled by this
3535
// class (since it is derived from VCFSimpleHeaderLine). Therefore instances of this class always
@@ -45,7 +45,7 @@ public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final
4545
}
4646
}
4747

48-
return super.getValidationFailure(vcfTargetVersion);
48+
return super.validateForVersion(vcfTargetVersion);
4949
}
5050

5151
}

src/main/java/htsjdk/variant/vcf/VCFSampleHeaderLine.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ public VCFSampleHeaderLine(String line, VCFHeaderVersion version) {
1515
// other tags. So let validateForVersion detect any version incompatibility, ie., if this is ever
1616
// called with a V3 version.
1717
super(VCFConstants.SAMPLE_HEADER_KEY, new VCF4Parser().parseLine(line, expectedTagOrder));
18-
validateForVersion(version);
18+
validateForVersionOrThrow(version);
1919
}
2020

2121
public VCFSampleHeaderLine(final Map<String, String> mapping) {
2222
super(VCFConstants.SAMPLE_HEADER_KEY, mapping);
2323
}
2424

2525
@Override
26-
public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final VCFHeaderVersion vcfTargetVersion) {
26+
public Optional<VCFValidationFailure<VCFHeaderLine>> validateForVersion(final VCFHeaderVersion vcfTargetVersion) {
2727
if (!vcfTargetVersion.isAtLeastAsRecentAs(VCFHeaderVersion.VCF4_0)) {
2828
final String message = String.format("%s header lines are not allowed in VCF version %s headers",
2929
getKey(),
@@ -36,7 +36,7 @@ public Optional<VCFValidationFailure<VCFHeaderLine>> getValidationFailure(final
3636
}
3737
}
3838

39-
return super.getValidationFailure(vcfTargetVersion);
39+
return super.validateForVersion(vcfTargetVersion);
4040
}
4141

4242
}

0 commit comments

Comments
 (0)