1 package org.broadinstitute.hellbender.utils.genotyper;
2 
3 import htsjdk.variant.variantcontext.Allele;
4 import org.broadinstitute.hellbender.utils.Utils;
5 
6 import java.util.AbstractList;
7 import java.util.Iterator;
8 import java.util.List;
9 
10 /**
11  * Minimal interface for random access to a collection of Alleles.
12  */
13 //Note: Names in this interface are unusual because of name clash in a subclass.
14 // For example the name of AlleleList.alleleCount() cannot be simply size(), as would be usual,
15 // because {@link ReadLikelihoods} implements AlleleList and SampleList and then size() would be ambiguous.
16 public interface AlleleList<A extends Allele>{
17 
newList(final List<A> alleles)18     static <A extends Allele> AlleleList<A> newList(final List<A> alleles) {
19         return new IndexedAlleleList<A>(alleles);
20     }
21 
22     /**
23      * Returns the number of alleles in this AlleleList.
24      */
numberOfAlleles()25     int numberOfAlleles();
26 
27     /**
28      * Returns the index of the given Allele in this AlleleList.
29      * Returns a negative number if the given allele is not present in this AlleleList.
30      * @throws IllegalArgumentException if allele is null.
31      */
indexOfAllele(final A allele)32     int indexOfAllele(final A allele);
33 
34     /**
35      * Returns the allele at the given index in this AlleleList.
36      * @throws IllegalArgumentException if index is negative or equal
37      * to or higher than the number of elements in this AlleleList {@link AlleleList#numberOfAlleles()}).
38      */
getAllele(final int index)39     A getAllele(final int index);
40 
41     /**
42      * Returns <code>true</code> if this AlleleList contains the specified allele
43      * and <code>false</code> otherwise.
44      */
containsAllele(final A allele)45     default boolean containsAllele(final A allele){
46         return indexOfAllele(allele) >= 0;
47     }
48 
49     @SuppressWarnings({"rawtypes"})
50     static final AlleleList EMPTY_LIST = new AlleleList() {
51         @Override
52         public int numberOfAlleles() {
53             return 0;
54         }
55 
56         @Override
57         public int indexOfAllele(final Allele allele) {
58             Utils.nonNull(allele);
59             return -1;
60         }
61 
62         @Override
63         public Allele getAllele(final int index) {
64             throw new IllegalArgumentException("allele index is out of range");  //we know this without checking because it's an empty list
65         }
66     };
67 
68     /**
69      * Returns an unmodifiable empty allele-list.
70      * @param <A> the allele class.
71      * @return never {@code null}.
72      */
73     @SuppressWarnings("unchecked")
emptyAlleleList()74     static <A extends Allele> AlleleList<A> emptyAlleleList() {
75         return (AlleleList<A>)EMPTY_LIST;
76     }
77 
78     /**
79      * Checks whether two allele lists are in fact the same.
80      * @param first one list to compare.
81      * @param second another list to compare.
82      *
83      * @throws IllegalArgumentException if if either list is {@code null}.
84      *
85      * @return {@code true} iff both list are equal.
86      */
equals(final AlleleList<A> first, final AlleleList<A> second)87     static <A extends Allele> boolean equals(final AlleleList<A> first, final AlleleList<A> second) {
88         if (first == null || second == null) {
89             throw new IllegalArgumentException("no null list allowed");
90         }
91         final int alleleCount = first.numberOfAlleles();
92         if (alleleCount != second.numberOfAlleles()) {
93             return false;
94         }
95 
96         for (int i = 0; i < alleleCount; i++) {
97             final A firstSample = first.getAllele(i);
98             Utils.nonNull(firstSample, "no null samples allowed in sample-lists: first list at " + i);
99             final A secondSample = second.getAllele(i);
100             Utils.nonNull(secondSample,"no null samples allowed in sample-list: second list at " + i);
101             if (!firstSample.equals(secondSample)) {
102                 return false;
103             }
104         }
105 
106         return true;
107     }
108 
109     /**
110      * Resolves the index of the reference allele in an allele-list.
111      *
112      * <p>
113      *     If there is no reference allele, it returns -1. If there is more than one reference allele,
114      *     it returns the first occurrence (lowest index).
115      * </p>
116      *
117      *
118      * @throws IllegalArgumentException if {@code list} is {@code null}.
119      *
120      * @return -1 if there is no reference allele, or a values in [0,{@code list.alleleCount()}).
121      */
indexOfReference()122     default int indexOfReference() {
123         final int alleleCount = this.numberOfAlleles();
124         for (int i = 0; i < alleleCount; i++) {
125             if (this.getAllele(i).isReference()) {
126                 return i;
127             }
128         }
129         return -1;
130     }
131 
132     /**
133      * Returns a {@link List} unmodifiable view of this allele-list
134      *
135      * @return never {@code null}.
136      */
asListOfAlleles()137     default public List<A> asListOfAlleles() {
138         return new AbstractList<A>() {
139 
140             @Override
141             public A get(final int index) {
142                 return AlleleList.this.getAllele(index);
143             }
144 
145             @Override
146             public int size() {
147                 return AlleleList.this.numberOfAlleles();
148             }
149         };
150     }
151 
152     /**
153      * Returns a permutation between two allele lists.
154      * @param target the target allele list.
155      *
156      * @throws IllegalArgumentException if {@code target} is {@code null}, or
157      * elements in {@code target} is not contained in {@code this}
158      *
159      * @return never {@code null}
160      */
161     default AlleleListPermutation<A> permutation(final AlleleList<A> target) {
162         if (equals(this, target)) {
163             return new NonPermutation<>(this);
164         } else {
165             return new ActualPermutation<>(this, target);
166         }
167     }
168 
169     /**
170      * This is the identity permutation.
171      */
172     final class NonPermutation<A extends Allele> implements AlleleListPermutation<A> {
173 
174         private final AlleleList<A> list;
175 
176         public NonPermutation(final AlleleList<A> original) {
177             list = original;
178         }
179 
180         @Override
181         public boolean isPartial() {
182             return false;
183         }
184 
185         @Override
186         public boolean isNonPermuted() {
187             return true;
188         }
189 
190         @Override
191         public int toIndex(final int fromIndex) {
192             return fromIndex;
193         }
194 
195         @Override
196         public int fromIndex(final int toIndex) {
197             return toIndex;
198         }
199 
200         @Override
201         public boolean isKept(final int fromIndex) { return true; }
202 
203         @Override
204         public int fromSize() {
205             return list.numberOfAlleles();
206         }
207 
208         @Override
209         public int toSize() {
210             return list.numberOfAlleles();
211         }
212 
213         @Override
214         public List<A> fromList() {
215             return list.asListOfAlleles();
216         }
217 
218         @Override
219         public List<A> toList() {
220             return list.asListOfAlleles();
221         }
222 
223         @Override
224         public int numberOfAlleles() {
225             return list.numberOfAlleles();
226         }
227 
228         @Override
229         public int indexOfAllele(final A allele) {
230             return list.indexOfAllele(allele);
231         }
232 
233         @Override
234         public A getAllele(final int index) {
235             return list.getAllele(index);
236         }
237     }
238 
239     final class ActualPermutation<A extends Allele> implements AlleleListPermutation<A> {
240 
241         private final AlleleList<A> from;
242 
243         private final AlleleList<A> to;
244 
245         private final int[] fromIndex;
246 
247         private final boolean[] keptFromIndices;
248 
249         private final boolean nonPermuted;
250 
251         private final boolean isPartial;
252 
253         private ActualPermutation(final AlleleList<A> original, final AlleleList<A> target) {
254             this.from = original;
255             this.to = target;
256             keptFromIndices = new boolean[original.numberOfAlleles()];
257             final int toSize = target.numberOfAlleles();
258             final int fromSize = original.numberOfAlleles();
259             if (fromSize < toSize) {
260                 throw new IllegalArgumentException("target allele list is not a permutation of the original allele list");
261             }
262 
263             fromIndex = new int[toSize];
264             boolean nonPermuted = fromSize == toSize;
265             this.isPartial = !nonPermuted;
266             for (int i = 0; i < toSize; i++) {
267                 final int originalIndex = original.indexOfAllele(target.getAllele(i));
268                 if (originalIndex < 0) {
269                     throw new IllegalArgumentException("target allele list is not a permutation of the original allele list");
270                 }
271                 keptFromIndices[originalIndex] = true;
272                 fromIndex[i] = originalIndex;
273                 nonPermuted &= originalIndex == i;
274             }
275 
276             this.nonPermuted = nonPermuted;
277         }
278 
279         @Override
280         public boolean isPartial() {
281             return isPartial;
282         }
283 
284         @Override
285         public boolean isNonPermuted() {
286             return nonPermuted;
287         }
288 
289         @Override
290         public int toIndex(final int fromIndex) {
291             return to.indexOfAllele(from.getAllele(fromIndex));
292         }
293 
294         @Override
295         public int fromIndex(final int toIndex) {
296             return fromIndex[toIndex];
297         }
298 
299         @Override
300         public boolean isKept(final int fromIndex) {
301             return keptFromIndices[fromIndex];
302         }
303 
304         @Override
305         public int fromSize() {
306             return from.numberOfAlleles();
307         }
308 
309         @Override
310         public int toSize() {
311             return to.numberOfAlleles();
312         }
313 
314         @Override
315         public List<A> fromList() {
316             return from.asListOfAlleles();
317         }
318 
319         @Override
320         public List<A> toList() {
321             return to.asListOfAlleles();
322         }
323 
324         @Override
325         public int numberOfAlleles() {
326             return to.numberOfAlleles();
327         }
328 
329         @Override
330         public int indexOfAllele(final A allele) {
331             return to.indexOfAllele(allele);
332         }
333 
334         @Override
335         public A getAllele(final int index) {
336             return to.getAllele(index);
337         }
338     }
339 }
340