File: ChromInterval.java

package info (click to toggle)
beagle 5.1-200518%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 9,480 kB
  • sloc: java: 14,430; sh: 55; makefile: 11
file content (356 lines) | stat: -rw-r--r-- 12,078 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
 * Copyright (C) 2014-2016 Brian L. Browning
 *
 * This file is part of Beagle
 *
 * Beagle is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Beagle is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package beagleutil;

import blbutil.Const;
import vcf.Marker;

/**
 * <p>Class {@code ChromInterval} represents a chromosome interval whose
 * end points are genome coordinates.
 * </p>
 *
 * Instances of class {@code ChromInterval} are immutable.
 *
 * @author Brian L. Browning {@code <browning@uw.edu>}
*/
public final class ChromInterval implements IntInterval,
        Comparable<ChromInterval> {

    private final int chromIndex;
    private final int start;
    private final int end;

    /**
     * Constructs a new {@code ChromInterval} instance.
     *
     * @param start the first marker in the interval.
     * @param end the last marker in the interval.
     *
     * @throws IllegalArgumentException if
     * {@code start.chromIndex()!=end.chromIndex() ||
     * start.pos()<0 || start.pos()>end.pos()}.
     * @throws NullPointerException if
     * {@code start==null || end==null}.
     */
    public ChromInterval(Marker start, Marker end) {
        if (start.chromIndex() != end.chromIndex()) {
            String s = "start.chromIndex() != end.chromIndex()";
            throw new IllegalArgumentException(s);
        }
        if (start.pos() < 0 || start.pos() > end.pos()) {
            String s = "start=" + start + " end=" + end;
            throw new IllegalArgumentException(s);
        }
        this.chromIndex = start.chromIndex();
        this.start = start.pos();
        this.end = end.pos();
    }

    /**
     * Constructs a new {@code ChromInterval} instance.
     *
     * @param chrom the chromosome,
     * @param start the first genome coordinate in the interval.
     * @param end the last genome coordinate in the interval.
     *
     * @throws IllegalArgumentException if
     * {@code start<0 || start>end || chrom.isEmpty()}.
     * @throws NullPointerException if {@code chrom==null}.
     */
    public ChromInterval(String chrom, int start, int end) {
        if (start < 0 || start > end) {
            String s = "start=" + start + " end=" + end;
            throw new IllegalArgumentException(s);
        }
        this.chromIndex = ChromIds.instance().getIndex(chrom);
        this.start = start;
        this.end = end;
    }

    /**
     * <p>Returns a {@code ChromInterval} instance corresponding to the
     * specified string, or returns {@code null} if the specified
     * string does not represent a valid chromosome interval or if the
     * specified string is {@code null}.
     * </p>
     * The string representation of the chromosome interval must have one
     * of the following forms:<br>
     * <pre>
     * [chrom]:[start]-[end]
     * [chrom]
     * [chrom]:
     * [chrom]:[start]-
     * [chrom]:-end
     * </pre>
     * where <br>
     * <br>
     * {@code [chrom]} is a chromosome identifier, and
     * {@code [start]} and {@code [end]} are non-negative
     * integers satisfying {@code [start]<=[end]}.  If the specified
     * string does not contain a start position, the {@code start()}
     * method of the returned {@code ChromInterval} instance returns 0.
     * If no end position is specified, the {@code end()} method of the
     * returned {@code ChromInterval} instance returns
     * {@code Integer.MAX_VALUE}.
     *
     * @param str a chromosome interval.
     * @return a {@code ChromInterval} instance corresponding to the
     * specified string, or returns {@code null} if the specified
     * string does not represent a valid chromosome interval or if the
     * specified string is {@code null}.
     */
    public static ChromInterval parse(String str) {
        if (str==null) {
            return null;
        }
        str = str.trim();
        int length = str.length();
        int start = 0;
        int end = Integer.MAX_VALUE;
        int chrDelim = str.lastIndexOf(Const.colon);
        int posDelim = str.lastIndexOf(Const.hyphen);
        if (length==0) {
            return null;
        }
        else if (chrDelim == -1) {
            return new ChromInterval(str, start, end);
        }
        else if (chrDelim == length-1) {
            return new ChromInterval(str.substring(0, length-1), start, end);
        }
        else {
            if ( (posDelim == -1) || (posDelim <= chrDelim)
                        || (chrDelim == length-2)
                        || (isValidPos(str, chrDelim+1, posDelim)==false)
                        || (isValidPos(str, posDelim+1, length)==false) ) {
                return null;
            }
            if (posDelim > chrDelim + 1) {
                start = Integer.parseInt(str.substring(chrDelim+1, posDelim));
            }
            if (length > posDelim + 1) {
                end = Integer.parseInt(str.substring(posDelim+1, length));
            }
            if (start < 0 || start > end) {
                return null;
            }
        }
        return new ChromInterval(str.substring(0, chrDelim), start, end);
    }

    /* endIndex is exclusive */
    private static boolean isValidPos(String s, int startIndex,
            int endIndex) {
        if (startIndex==endIndex) {
            return true;
        }
        int length = endIndex - startIndex;
        if ((length > 1) && s.charAt(startIndex)==0) {
            return false;
        }
        for (int j=startIndex; j<endIndex; ++j) {
            char c = s.charAt(j);
            if (Character.isDigit(c)==false) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the chromosome index.  The chromosome index is equal to
     * {@code ChromIds.indexOf(this.chrom())}.
     * @return the chromosome index.
     */
    public int chromIndex() {
        return chromIndex;
    }

    /**
     * Returns the chromosome identifier.
     * @return the chromosome identifier.
     */
    public String chrom() {
        return ChromIds.instance().id(chromIndex);
    }

    /**
     * Returns the first genome coordinate in this chromosome interval.
     * @return the first genome coordinate in this chromosomet interval.
     */
    @Override
    public int start() {
        return start;
    }

    /**
     * Returns the last genome coordinate in this chromosome interval.
     * @return the last genome coordinate in this chromosome interval.
     */
    @Override
    public int inclEnd() {
        return end;
    }

    /**
     * <p>Compares this {@code ChromInteval} with the specified
     * {@code ChromInterval} instance for order, and
     * returns -1, 0, or 1 depending on whether {@code this}
     * is less than, equal or greater than the specified instance.
     * </p>
     * {@code ChromInterval} objects are ordered first by
     * {@code this.chromIndex()}, then by
     * {@code this.start()}, and finally by {@code this.end()}.
     * All fields are ordered in ascending order.
     * @param o the {@code ChromInterval} to be compared with {@code this}.
     * @return -1, 0, or 1 depending on whether {@code this}
     * is less than, equal or greater than the specified instance.
     */
    @Override
    public int compareTo(ChromInterval o) {
        if (this.chromIndex != o.chromIndex) {
            return (this.chromIndex < o.chromIndex) ? -1 : 1;
        }
        if (this.start != o.start) {
            return (this.start < o.start) ? -1 : 1;
        }
        if (this.end != o.end) {
            return (this.end < o.end) ? -1 : 1;
        }
        return 0;
    }

    /**
     * <p>Returns a hash code value for the object.
     * </p>
     * <p>The hash code is defined by the following calculation:
     * </p>
     * <pre>
        int hash = 7;
        hash = 67 * hash + this.chromIndex();
        hash = 67 * hash + this.start();
        hash = 67 * hash + this.end();
     * </pre>
     * @return a hash code value for the object.
     */
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 67 * hash + this.chromIndex;
        hash = 67 * hash + this.start;
        hash = 67 * hash + this.end;
        return hash;
    }

    /**
     * Returns {@code true} if the specified object is a
     * {@code ChromInterval} instance representing the same
     * interval of genome coordinates as {@code this}, and
     * returns {@code false} otherwise.
     *
     * @param obj the object to be compared with {@code this} for
     * equality.
     * @return {@code true} if the specified object is a
     * {@code ChromInterval} instance representing the same
     * interval of genome coordinates as {@code this}, and
     * returns {@code false} otherwise.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ChromInterval other = (ChromInterval) obj;
        if (this.chromIndex != other.chromIndex) {
            return false;
        }
        if (this.start != other.start) {
            return false;
        }
        return this.end == other.end;
    }

    /**
     * Returns the string:
     * {@code this.chrom() + ":" + this.start() + "-" + this.end()}
     * @return {@code this.chrom() + ":" + this.start() + "-" + this.end()}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(ChromIds.instance().id(chromIndex));
        sb.append(Const.colon);
        sb.append(start);
        sb.append(Const.hyphen);
        sb.append(end);
        return sb.toString();
    }

    /**
     * Returns {@code true} if the specified marker is in this chromosome
     * interval and returns {@code false} otherwise.
     * @param marker a marker
     * @return {@code true} if the specified marker is in this chromosome
     * interval
     * @throws NullPointerException if {@code marker == null}
     */
    public boolean contains(Marker marker) {
        int pos = marker.pos();
        return marker.chromIndex()==chromIndex && start <= pos && pos <= end;
    }

    /**
     * Returns {@code true} if the specified chromosome intervals
     * have non-empty intersection and returns {@code false} otherwise.
     * @param a a chromosome interval.
     * @param b a chromosome interval.
     * @return {@code true} if the specified chromosome intervals
     * have non-empty intersection and returns {@code false} otherwise.
     */
    public static boolean overlap(ChromInterval a, ChromInterval b) {
        if (a.chromIndex() != b.chromIndex()) {
            return false;
        }
        else {
            return (a.start() <= b.inclEnd()) && (b.start() <= a.inclEnd());
        }
    }

    /**
     * Returns the union of the specified overlapping chromosome intervals.
     * @param a a chromosome interval.
     * @param b a chromosome interval.
     * @return the union of the specified overlapping chromosome intervals.
     * @throws IllegalArgumentException if
     * {@code ChromInterval.overlap(a, b)==false}.
     */
    public static ChromInterval merge(ChromInterval a, ChromInterval b) {
        if (overlap(a, b)==false) {
            String s = "non-overlappng intervals: " + a + " " + b;
            throw new IllegalArgumentException(s);
        }
        int start = Math.min(a.start(), b.start());
        int end = Math.max(a.inclEnd(), b.inclEnd());
        return new ChromInterval(a.chrom(), start, end);
    }
}