/* * Copyright (c) 2012 The Broad Institute * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package htsjdk.variant.variantcontext; import htsjdk.variant.vcf.VCFConstants; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Builder class for VariantContext * * Some basic assumptions here: * * 1 -- data isn't protectively copied. If you provide an attribute map to * the build, and modify it later, the builder will see this and so will any * resulting variant contexts. It's best not to modify collections provided * to a builder. * * 2 -- the system uses the standard builder model, allowing the simple construction idiom: * * builder.source("a").genotypes(gc).id("x").make() => VariantContext * * 3 -- The best way to copy a VariantContext is: * * new VariantContextBuilder(vc).make() => a copy of VC * * 4 -- validation of arguments is done at the during the final make() call, so a * VariantContextBuilder can exist in an inconsistent state as long as those issues * are resolved before the call to make() is issued. * * @author depristo */ public class VariantContextBuilder { // required fields private boolean fullyDecoded = false; private String source = null; private String contig = null; private long start = -1; private long stop = -1; private Collection<Allele> alleles = null; // optional -> these are set to the appropriate default value private String ID = VCFConstants.EMPTY_ID_FIELD; private GenotypesContext genotypes = GenotypesContext.NO_GENOTYPES; private double log10PError = VariantContext.NO_LOG10_PERROR; private Set<String> filters = null; private Map<String, Object> attributes = null; private boolean attributesCanBeModified = false; /** enum of what must be validated */ final private EnumSet<VariantContext.Validation> toValidate = EnumSet.noneOf(VariantContext.Validation.class); /** * Create an empty VariantContextBuilder where all values adopt their default values. Note that * source, chr, start, stop, and alleles must eventually be filled in, or the resulting VariantContext * will throw an error. */ public VariantContextBuilder() {} /** * Create an empty VariantContextBuilder where all values adopt their default values, but the bare min. * of info (source, chr, start, stop, and alleles) have been provided to start. */ public VariantContextBuilder(String source, String contig, long start, long stop, Collection<Allele> alleles) { this.source = source; this.contig = contig; this.start = start; this.stop = stop; this.alleles = alleles; this.attributes = Collections.emptyMap(); // immutable toValidate.add(VariantContext.Validation.ALLELES); } /** * Returns a new builder based on parent -- the new VC will have all fields initialized * to their corresponding values in parent. This is the best way to create a derived VariantContext * * @param parent Cannot be null */ public VariantContextBuilder(VariantContext parent) { if ( parent == null ) throw new IllegalArgumentException("BUG: VariantContextBuilder parent argument cannot be null in VariantContextBuilder"); this.alleles = parent.alleles; this.attributes = parent.getAttributes(); this.attributesCanBeModified = false; this.contig = parent.contig; this.filters = parent.getFiltersMaybeNull(); this.genotypes = parent.genotypes; this.ID = parent.getID(); this.log10PError = parent.getLog10PError(); this.source = parent.getSource(); this.start = parent.getStart(); this.stop = parent.getEnd(); this.fullyDecoded = parent.isFullyDecoded(); } public VariantContextBuilder(VariantContextBuilder parent) { if ( parent == null ) throw new IllegalArgumentException("BUG: VariantContext parent argument cannot be null in VariantContextBuilder"); this.alleles = parent.alleles; this.attributesCanBeModified = false; this.contig = parent.contig; this.genotypes = parent.genotypes; this.ID = parent.ID; this.log10PError = parent.log10PError; this.source = parent.source; this.start = parent.start; this.stop = parent.stop; this.fullyDecoded = parent.fullyDecoded; this.attributes(parent.attributes); this.filters(parent.filters); } public VariantContextBuilder copy() { return new VariantContextBuilder(this); } /** * Tells this builder to use this collection of alleles for the resulting VariantContext * * @param alleles * @return this builder */ public VariantContextBuilder alleles(final Collection<Allele> alleles) { this.alleles = alleles; toValidate.add(VariantContext.Validation.ALLELES); return this; } public VariantContextBuilder alleles(final List<String> alleleStrings) { List<Allele> alleles = new ArrayList<Allele>(alleleStrings.size()); for ( int i = 0; i < alleleStrings.size(); i++ ) { alleles.add(Allele.create(alleleStrings.get(i), i == 0)); } return alleles(alleles); } public VariantContextBuilder alleles(final String ... alleleStrings) { return alleles(Arrays.asList(alleleStrings)); } public List<Allele> getAlleles() { return new ArrayList<Allele>(alleles); } /** * Tells this builder to use this map of attributes alleles for the resulting VariantContext * * Attributes can be null -> meaning there are no attributes. After * calling this routine the builder assumes it can modify the attributes * object here, if subsequent calls are made to set attribute values * @param attributes */ public VariantContextBuilder attributes(final Map<String, Object> attributes) { if (attributes != null) { this.attributes = attributes; } else { this.attributes = new HashMap<String, Object>(); } this.attributesCanBeModified = true; return this; } /** * Puts the key -> value mapping into this builder's attributes * * @param key * @param value * @return */ public VariantContextBuilder attribute(final String key, final Object value) { makeAttributesModifiable(); attributes.put(key, value); return this; } /** * Removes key if present in the attributes * * @param key key to remove * @return */ public VariantContextBuilder rmAttribute(final String key) { makeAttributesModifiable(); attributes.remove(key); return this; } /** * Removes list of keys if present in the attributes * * @param keys list of keys to remove * @return */ public VariantContextBuilder rmAttributes(final List<String> keys) { makeAttributesModifiable(); for ( final String key : keys ) attributes.remove(key); return this; } /** * Makes the attributes field modifiable. In many cases attributes is just a pointer to an immutable * collection, so methods that want to add / remove records require the attributes to be copied to a */ private void makeAttributesModifiable() { if ( ! attributesCanBeModified ) { this.attributesCanBeModified = true; if (attributes == null) { this.attributes = new HashMap<String, Object>(); } else { this.attributes = new HashMap<String, Object>(attributes); } } } /** * This builder's filters are set to this value * * filters can be null -> meaning there are no filters * @param filters */ public VariantContextBuilder filters(final Set<String> filters) { this.filters = filters; return this; } /** * {@link #filters} * * @param filters * @return */ public VariantContextBuilder filters(final String ... filters) { filters(new LinkedHashSet<String>(Arrays.asList(filters))); return this; } public VariantContextBuilder filter(final String filter) { if ( this.filters == null ) this.filters = new LinkedHashSet<String>(1); this.filters.add(filter); return this; } /** * Tells this builder that the resulting VariantContext should have PASS filters * * @return */ public VariantContextBuilder passFilters() { return filters(VariantContext.PASSES_FILTERS); } /** * Tells this builder that the resulting VariantContext be unfiltered * * @return */ public VariantContextBuilder unfiltered() { this.filters = null; return this; } /** * Tells this builder that the resulting VariantContext should use this genotypes GenotypeContext * * Note that genotypes can be null -> meaning there are no genotypes * * @param genotypes */ public VariantContextBuilder genotypes(final GenotypesContext genotypes) { this.genotypes = genotypes; if ( genotypes != null ) toValidate.add(VariantContext.Validation.GENOTYPES); return this; } public VariantContextBuilder genotypesNoValidation(final GenotypesContext genotypes) { this.genotypes = genotypes; return this; } /** * Tells this builder that the resulting VariantContext should use a GenotypeContext containing genotypes * * Note that genotypes can be null -> meaning there are no genotypes * * @param genotypes */ public VariantContextBuilder genotypes(final Collection<Genotype> genotypes) { return genotypes(GenotypesContext.copy(genotypes)); } /** * Tells this builder that the resulting VariantContext should use a GenotypeContext containing genotypes * @param genotypes */ public VariantContextBuilder genotypes(final Genotype ... genotypes) { return genotypes(GenotypesContext.copy(Arrays.asList(genotypes))); } /** * Tells this builder that the resulting VariantContext should not contain any GenotypeContext */ public VariantContextBuilder noGenotypes() { this.genotypes = null; return this; } /** * Tells us that the resulting VariantContext should have ID * @param ID * @return */ public VariantContextBuilder id(final String ID) { this.ID = ID; return this; } /** * Tells us that the resulting VariantContext should not have an ID * @return */ public VariantContextBuilder noID() { return id(VCFConstants.EMPTY_ID_FIELD); } /** * Tells us that the resulting VariantContext should have log10PError * @param log10PError * @return */ public VariantContextBuilder log10PError(final double log10PError) { this.log10PError = log10PError; return this; } /** * Tells us that the resulting VariantContext should have source field set to source * @param source * @return */ public VariantContextBuilder source(final String source) { this.source = source; return this; } /** * Tells us that the resulting VariantContext should have the specified location * @param contig * @param start * @param stop * @return */ public VariantContextBuilder loc(final String contig, final long start, final long stop) { this.contig = contig; this.start = start; this.stop = stop; toValidate.add(VariantContext.Validation.ALLELES); return this; } /** * Tells us that the resulting VariantContext should have the specified contig chr * @param contig * @return */ public VariantContextBuilder chr(final String contig) { this.contig = contig; return this; } /** * Tells us that the resulting VariantContext should have the specified contig start * @param start * @return */ public VariantContextBuilder start(final long start) { this.start = start; toValidate.add(VariantContext.Validation.ALLELES); return this; } /** * Tells us that the resulting VariantContext should have the specified contig stop * @param stop * @return */ public VariantContextBuilder stop(final long stop) { this.stop = stop; return this; } /** * @see #computeEndFromAlleles(java.util.List, int, int) with endForSymbolicAlleles == -1 */ public VariantContextBuilder computeEndFromAlleles(final List<Allele> alleles, final int start) { return computeEndFromAlleles(alleles, start, -1); } /** * Compute the end position for this VariantContext from the alleles themselves * * assigns this builder the stop position computed. * * @param alleles the list of alleles to consider. The reference allele must be the first one * @param start the known start position of this event * @param endForSymbolicAlleles the end position to use if any of the alleles is symbolic. Can be -1 * if no is expected but will throw an error if one is found * @return this builder */ public VariantContextBuilder computeEndFromAlleles(final List<Allele> alleles, final int start, final int endForSymbolicAlleles) { stop(VariantContextUtils.computeEndFromAlleles(alleles, start, endForSymbolicAlleles)); return this; } /** * @return true if this builder contains fully decoded data * * See VariantContext for more information */ public boolean isFullyDecoded() { return fullyDecoded; } /** * Sets this builder's fully decoded state to true. * * A fully decoded builder indicates that all fields are represented by their * proper java objects (e.g., Integer(10) not "10"). * * See VariantContext for more information * * @param isFullyDecoded */ public VariantContextBuilder fullyDecoded(boolean isFullyDecoded) { this.fullyDecoded = isFullyDecoded; return this; } /** * Takes all of the builder data provided up to this point, and instantiates * a freshly allocated VariantContext with all of the builder data. This * VariantContext is validated as appropriate and if not failing QC (and * throwing an exception) is returned. * * Note that this function can be called multiple times to create multiple * VariantContexts from the same builder. */ public VariantContext make() { return new VariantContext(source, ID, contig, start, stop, alleles, genotypes, log10PError, filters, attributes, fullyDecoded, toValidate); } }