/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2015 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 org.broad.igv.feature;
import com.google.common.base.Objects;
import org.broad.igv.feature.genome.Genome;
import org.broad.igv.track.WindowFunction;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//~--- JDK imports ------------------------------------------------------------
/**
* A sub region of a feature. For example, a Gene exon
*
* @author jrobinso
*/
public class Exon extends AbstractFeature implements IExon {
/**
* Index relative to the 5' end.
*/
private int number;
/**
* Coding start position. This is the leftmost position of the coding region, not necessarily the 5'utr end
*/
private int codingStart;
private int codingEnd;
private AminoAcidSequence aminoAcidSequence;
//Raw bytes representing nucleotides
//Stored separately from the aminoAcidSequence because the latter changes
//when we change translation tables
private byte[] seqBytes;
private boolean noncoding = false;
// The position of the first base of this exon relative to the start of the mRNA. This will correspond
// to either the beginning or end of the exon, depending on the strand
private int mrnaBase = -1;
public void setMrnaBase(int base) {
this.mrnaBase = base;
}
/**
* Get amino acid number based on genomic coordinate.
* Genome coordinate MUST be 0-based
*
* @param genomeCoordinate
* @return
*/
public int getAminoAcidNumber(int genomeCoordinate) {
if (mrnaBase < 0) {
return -1;
}
if (genomeCoordinate < getStart() || genomeCoordinate > getEnd()) {
throw new IndexOutOfBoundsException();
}
if (getStrand() == Strand.POSITIVE) {
int mrnaCoord = mrnaBase + (genomeCoordinate - codingStart);
return mrnaCoord < 0 ? -1 : mrnaCoord / 3 + 1;
} else if (getStrand() == Strand.NEGATIVE) {
//Since codingEnd is exclusive-end, we subtract 1 from it
//We want mrnaCoord = 0 when genomeCoordinate == codingEnd - 1
int mrnaCoord = mrnaBase + (codingEnd - 1 - genomeCoordinate);
return mrnaCoord < 0 ? -1 : mrnaCoord / 3 + 1;
} else {
return 0;
}
}
public Exon(String chr, int start, int end, Strand strand) {
super(chr, start, end, strand);
// By default the entire exon is a coding region
this.codingStart = start;
this.codingEnd = end;
}
public Exon(Exon bf) {
this.start = bf.getStart();
this.end = bf.getEnd();
this.strand = bf.getStrand();
this.codingStart = bf.getCdStart();
this.codingEnd = bf.getCdEnd();
this.chromosome = bf.getChr();
this.type = bf.getType();
this.color = bf.getColor();
this.description = bf.getDescription();
this.attributes = bf.getAttributes();
this.name = bf.getName();
this.readingFrame = bf.getReadingFrame();
this.noncoding = (type != null && SequenceOntology.utrTypes.contains(type));
}
public Exon(BasicFeature bf) {
this.start = bf.getStart();
this.end = bf.getEnd();
this.strand = bf.getStrand();
this.codingStart = bf.getThickStart();
this.codingEnd = bf.getThickEnd();
this.chromosome = bf.getChr();
this.type = bf.getType();
this.color = bf.getColor();
this.description = bf.getDescription();
this.attributes = bf.getAttributes();
this.name = bf.getName();
this.readingFrame = bf.getReadingFrame();
this.noncoding = (type != null && SequenceOntology.utrTypes.contains(type));
}
/**
* Flag indicating that the entire exon is non-coding.
*
* @param bool
*/
public void setNonCoding(boolean bool) {
this.noncoding = bool;
if (bool) {
if (getStrand() == Strand.POSITIVE) {
codingStart = codingEnd = getEnd();
} else {
codingStart = codingEnd = getStart();
}
}
}
public boolean isNonCoding() {
return noncoding;
}
public void setCodingStart(int codingStart) {
this.codingStart = Math.max(getStart(), codingStart);
}
public void setCodingEnd(int codingEnd) {
this.codingEnd = Math.min(getEnd(), codingEnd);
}
public void setPhase(int phase) {
if (getStrand() == Strand.POSITIVE) {
readingFrame = phase;
} else if (getStrand() == Strand.NEGATIVE) {
int modLen = (getCodingLength() - phase) % 3;
readingFrame = modLen;
}
}
public int getCdStart() {
return codingStart;
}
public int getCdEnd() {
return this.codingEnd;
}
public int getCodingLength() {
return noncoding ? 0 : Math.max(0, codingEnd - codingStart);
}
public AminoAcidSequence getAminoAcidSequence(Genome genome, Exon prevExon, Exon nextExon) {
if (aminoAcidSequence == null ||
//If the stored sequence was computed with a different codon table, we reset
!(Objects.equal(aminoAcidSequence.getCodonTableKey(), AminoAcidManager.getInstance().getCodonTable().getKey()))) {
computeAminoAcidSequence(genome, prevExon, nextExon);
}
return aminoAcidSequence;
}
private void computeAminoAcidSequence(Genome genome, Exon prevExon, Exon nextExon) {
if (noncoding) {
return;
}
int start = getStart();
int end = getEnd();
String chr = getChr();
if (readingFrame >= 0) {
int readStart = (codingStart > start) ? codingStart : start;
int readEnd = Math.min(end, codingEnd);
if (readEnd > readStart + 3) {
if (seqBytes == null) {
seqBytes = genome.getSequence(chr, readStart, readEnd);
}
if (seqBytes != null) {
// Grab nucleotides from previous exon if needed to complete first codon
if (readingFrame > 0 && prevExon != null) {
int diff = 3 - readingFrame;
byte[] d = genome.getSequence(chr, prevExon.getCdEnd() - diff, prevExon.getCdEnd());
byte[] tmp = new byte[d.length + seqBytes.length];
System.arraycopy(d, 0, tmp, 0, diff);
System.arraycopy(seqBytes, 0, tmp, diff, seqBytes.length);
seqBytes = tmp;
readStart -= diff;
}
// Grab nucleotides from next exon if needed for last codon
int diff = 3 - ((readEnd - (codingStart + readingFrame)) % 3);
if (diff > 0 && diff < 3 && nextExon != null) {
byte[] d = genome.getSequence(chr, nextExon.getCdStart(), nextExon.getCdStart() + diff);
byte[] tmp = new byte[d.length + seqBytes.length];
System.arraycopy(seqBytes, 0, tmp, 0, seqBytes.length);
System.arraycopy(d, 0, tmp, seqBytes.length, d.length);
seqBytes = tmp;
}
aminoAcidSequence = AminoAcidManager.getInstance().getAminoAcidSequence(getStrand(), readStart, new String(seqBytes));
}
}
}
}
public Exon copy() {
Exon copy = new Exon(getChr(), getStart(), getEnd(), getStrand());
copy.seqBytes = this.seqBytes;
copy.aminoAcidSequence = this.aminoAcidSequence;
copy.codingEnd = this.codingEnd;
copy.codingStart = this.codingStart;
copy.name = this.name;
copy.noncoding = this.noncoding;
copy.mrnaBase = this.mrnaBase;
return copy;
}
public String getValueString(double position, int mouseX, WindowFunction windowFunction) {
StringBuffer buffer = new StringBuffer();
if (number > 0) buffer.append("Exon number: " + number + "<br>");
int aaNumber = this.getAminoAcidNumber((int) position);
if (aaNumber > 0) {
buffer.append("Amino acid coding number: " + aaNumber + "<br>");
}
buffer.append(getLocusString());
if (description != null) buffer.append("<br>" + description);
if (attributes != null) {
buffer.append(getAttributeString());
}
return buffer.toString();
}
public void setNumber(int number) {
this.number = number;
}
public String getURL() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
public static IExon getExonProxy(IExon exon) {
InvocationHandler handler = new ExonLocHandler(exon);
IExon eProx = (IExon) Proxy.newProxyInstance(IExon.class.getClassLoader(),
new Class[]{IExon.class},
handler);
return eProx;
}
private static class ExonLocHandler implements InvocationHandler {
private IExon parent;
private int hashCode = 0;
public ExonLocHandler(IExon parent) {
this.parent = parent;
}
private boolean equals(IExon parent, Object inother) {
if (inother == null || !(inother instanceof IExon)) {
return false;
}
IExon other = (IExon) inother;
boolean eq = parent.getChr().equals(other.getChr());
eq &= parent.getStart() == other.getStart();
eq &= parent.getEnd() == other.getEnd();
eq &= parent.getCdStart() == other.getCdStart();
eq &= parent.getCdEnd() == other.getCdEnd();
eq &= parent.getStrand() == other.getStrand();
return eq;
}
private int hashCode(IExon parent) {
if (hashCode != 0) {
return hashCode;
}
String conc = parent.getChr() + parent.getStrand().toString() + parent.getStart();
conc += parent.getEnd();
conc += parent.getCdStart();
conc += parent.getCdEnd();
int hc = conc.hashCode();
if (hc == 0) {
hc = 1;
}
hashCode = hc;
return hc;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("hashCode")) {
return hashCode(parent);
} else if (method.getName().equals("equals")) {
return equals(parent, args[0]);
} else {
return method.invoke(parent, args);
}
}
}
}