/* * 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.tribble.util.ParsingUtils; import htsjdk.variant.vcf.VCFConstants; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A builder class for genotypes * * Provides convenience setter methods for all of the Genotype field * values. Setter methods can be used in any order, allowing you to * pass through states that wouldn't be allowed in the highly regulated * immutable Genotype class. * * All fields default to meaningful MISSING values. * * Call make() to actually create the corresponding Genotype object from * this builder. Can be called multiple times to create independent copies, * or with intervening sets to conveniently make similar Genotypes with * slight modifications. * * @author Mark DePristo * @since 06/12 */ public final class GenotypeBuilder { private static final List<Allele> HAPLOID_NO_CALL = Arrays.asList(Allele.NO_CALL); private static final List<Allele> DIPLOID_NO_CALL = Arrays.asList(Allele.NO_CALL, Allele.NO_CALL); private String sampleName = null; private List<Allele> alleles = Collections.emptyList(); private boolean isPhased = false; private int GQ = -1; private int DP = -1; private int[] AD = null; private int[] PL = null; private Map<String, Object> extendedAttributes = null; private String filters = null; private int initialAttributeMapSize = 5; private final static Map<String, Object> NO_ATTRIBUTES = Collections.unmodifiableMap(new HashMap<String, Object>(0)); // ----------------------------------------------------------------- // // Factory methods // // ----------------------------------------------------------------- public static Genotype create(final String sampleName, final List<Allele> alleles) { return new GenotypeBuilder(sampleName, alleles).make(); } public static Genotype create(final String sampleName, final List<Allele> alleles, final Map<String, Object> attributes) { return new GenotypeBuilder(sampleName, alleles).attributes(attributes).make(); } protected static Genotype create(final String sampleName, final List<Allele> alleles, final double[] gls) { return new GenotypeBuilder(sampleName, alleles).PL(gls).make(); } /** * Create a new Genotype object for a sample that's missing from the VC (i.e., in * the output header). Defaults to a diploid no call genotype ./. * * @param sampleName the name of this sample * @return an initialized Genotype with sampleName that's a diploid ./. no call genotype */ public static Genotype createMissing(final String sampleName, final int ploidy) { final GenotypeBuilder builder = new GenotypeBuilder(sampleName); switch ( ploidy ) { case 1: builder.alleles(HAPLOID_NO_CALL); break; case 2: builder.alleles(DIPLOID_NO_CALL); break; default: builder.alleles(Collections.nCopies(ploidy, Allele.NO_CALL)); break; } return builder.make(); } /** * Create a empty builder. Both a sampleName and alleles must be provided * before trying to make a Genotype from this builder. */ public GenotypeBuilder() {} /** * Create a builder using sampleName. Alleles must be provided * before trying to make a Genotype from this builder. * @param sampleName */ public GenotypeBuilder(final String sampleName) { name(sampleName); } /** * Make a builder using sampleName and alleles for starting values * @param sampleName * @param alleles */ public GenotypeBuilder(final String sampleName, final List<Allele> alleles) { name(sampleName); alleles(alleles); } /** * Create a new builder starting with the values in Genotype g * @param g */ public GenotypeBuilder(final Genotype g) { copy(g); } /** * Copy all of the values for this builder from Genotype g * @param g * @return */ public GenotypeBuilder copy(final Genotype g) { name(g.getSampleName()); alleles(g.getAlleles()); phased(g.isPhased()); GQ(g.getGQ()); DP(g.getDP()); AD(g.getAD()); PL(g.getPL()); filter(g.getFilters()); attributes(g.getExtendedAttributes()); return this; } /** * Reset all of the builder attributes to their defaults. After this * function you must provide sampleName and alleles before trying to * make more Genotypes. */ public final void reset(final boolean keepSampleName) { if ( ! keepSampleName ) sampleName = null; alleles = Collections.emptyList(); isPhased = false; GQ = -1; DP = -1; AD = null; PL = null; filters = null; extendedAttributes = null; } /** * Create a new Genotype object using the values set in this builder. * * After creation the values in this builder can be modified and more Genotypes * created, althrough the contents of array values like PL should never be modified * inline as they are not copied for efficiency reasons. * * @return a newly minted Genotype object with values provided from this builder */ public Genotype make() { final Map<String, Object> ea = extendedAttributes == null ? NO_ATTRIBUTES : extendedAttributes; return new FastGenotype(sampleName, alleles, isPhased, GQ, DP, AD, PL, filters, ea); } /** * Set this genotype's name * @param sampleName * @return */ public GenotypeBuilder name(final String sampleName) { this.sampleName = sampleName; return this; } /** * Set this genotype's alleles * @param alleles * @return */ public GenotypeBuilder alleles(final List<Allele> alleles) { if ( alleles == null ) this.alleles = Collections.emptyList(); else this.alleles = alleles; return this; } /** * Is this genotype phased? * @param phased * @return */ public GenotypeBuilder phased(final boolean phased) { isPhased = phased; return this; } public GenotypeBuilder GQ(final int GQ) { this.GQ = GQ; return this; } /** * Adaptor interface from the pLog10Error system. * * Will be retired when * * @param pLog10Error * @return */ @Deprecated public GenotypeBuilder log10PError(final double pLog10Error) { if ( pLog10Error == CommonInfo.NO_LOG10_PERROR ) return GQ(-1); else return GQ((int)Math.round(pLog10Error * -10)); } /** * This genotype has no GQ value * @return */ public GenotypeBuilder noGQ() { GQ = -1; return this; } /** * This genotype has no AD value * @return */ public GenotypeBuilder noAD() { AD = null; return this; } /** * This genotype has no DP value * @return */ public GenotypeBuilder noDP() { DP = -1; return this; } /** * This genotype has no PL value * @return */ public GenotypeBuilder noPL() { PL = null; return this; } /** * This genotype has this DP value * @return */ public GenotypeBuilder DP(final int DP) { this.DP = DP; return this; } /** * This genotype has this AD value * @return */ public GenotypeBuilder AD(final int[] AD) { this.AD = AD; return this; } /** * This genotype has this PL value, as int[]. FAST * @return */ public GenotypeBuilder PL(final int[] PL) { this.PL = PL; return this; } /** * This genotype has this PL value, converted from double[]. SLOW * @return */ public GenotypeBuilder PL(final double[] GLs) { this.PL = GenotypeLikelihoods.fromLog10Likelihoods(GLs).getAsPLs(); return this; } /** * This genotype has these attributes. * * Cannot contain inline attributes (DP, AD, GQ, PL) * @return */ public GenotypeBuilder attributes(final Map<String, Object> attributes) { for ( Map.Entry<String, Object> pair : attributes.entrySet() ) attribute(pair.getKey(), pair.getValue()); return this; } /** * Tells this builder to remove all extended attributes * * @return */ public GenotypeBuilder noAttributes() { this.extendedAttributes = null; return this; } /** * This genotype has this attribute key / value pair. * * Cannot contain inline attributes (DP, AD, GQ, PL) * @return */ public GenotypeBuilder attribute(final String key, final Object value) { if ( extendedAttributes == null ) extendedAttributes = new HashMap<String, Object>(initialAttributeMapSize); extendedAttributes.put(key, value); return this; } /** * Tells this builder to make a Genotype object that has had filters applied, * which may be empty (passes) or have some value indicating the reasons * why it's been filtered. * * @param filters non-null list of filters. empty list => PASS * @return this builder */ public GenotypeBuilder filters(final List<String> filters) { if ( filters.isEmpty() ) return filter(null); else if ( filters.size() == 1 ) return filter(filters.get(0)); else return filter(ParsingUtils.join(";", ParsingUtils.sortList(filters))); } /** * varargs version of #filters * @param filters * @return */ public GenotypeBuilder filters(final String ... filters) { return filters(Arrays.asList(filters)); } /** * Most efficient version of setting filters -- just set the filters string to filters * * @param filter if filters == null or filters.equals("PASS") => genotype is PASS * @return */ public GenotypeBuilder filter(final String filter) { this.filters = VCFConstants.PASSES_FILTERS_v4.equals(filter) ? null : filter; return this; } /** * This genotype is unfiltered * * @return */ public GenotypeBuilder unfiltered() { return filter(null); } /** * Tell's this builder that we have at most these number of attributes * @return */ public GenotypeBuilder maxAttributes(final int i) { initialAttributeMapSize = i; return this; } }