package bgu.bio.ds.rna;
import gnu.trove.list.array.TCharArrayList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import bgu.bio.adt.queue.Pool;
import bgu.bio.adt.tuples.IntPair;
import bgu.bio.util.CharBuffer;
import bgu.bio.util.alphabet.StemStructureAlphabet;
public class StemStructure {
private static Pool<InnerStructure> innerStructurePool;
private String header;
private TCharArrayList originalSequence;
private TCharArrayList originalStructure;
private TCharArrayList stemLoopSequence;
private TCharArrayList danglingLeft;
private TCharArrayList danglingRight;
private TCharArrayList backbone;
StemStructureShuffleData shuffleInformation;
/**
* Holds information on the inner structure in a manner that allows lookup by position in the
* backbone
*/
private TIntObjectHashMap<InnerStructure> innerStructureMap;
private static final StemStructureAlphabet structureAlphabet = StemStructureAlphabet
.getInstance();
static {
innerStructurePool = new Pool<InnerStructure>();
}
public StemStructure(String sequence, String structure) {
this(null, sequence, structure);
}
public StemStructure(String header, String sequence, String structure) {
innerStructureMap = new TIntObjectHashMap<InnerStructure>();
init(header, new TCharArrayList(sequence.toCharArray()),
new TCharArrayList(structure.toCharArray()));
}
public StemStructure(CharBuffer sequence, CharBuffer structure) {
this(null, sequence, structure);
}
public StemStructure(TCharArrayList seq, TCharArrayList str) {
this(null, seq, str);
}
public StemStructure(String header, TCharArrayList seq, TCharArrayList str) {
innerStructureMap = new TIntObjectHashMap<InnerStructure>();
init(header, seq, str);
}
public StemStructure(String header, CharBuffer sequence, CharBuffer structure) {
innerStructureMap = new TIntObjectHashMap<InnerStructure>();
init(header, charBuffer2List(sequence), charBuffer2List(structure));
}
private TCharArrayList charBuffer2List(CharBuffer cb) {
TCharArrayList lst = new TCharArrayList();
for (int i = 0; i < cb.length(); i++) {
lst.add(cb.charAt(i));
}
return lst;
}
private void init(String header, TCharArrayList sequence, TCharArrayList structure) {
this.header = header;
if (this.originalSequence == null) {
this.originalSequence = new TCharArrayList();
this.originalStructure = new TCharArrayList();
} else {
this.originalSequence.resetQuick();
this.originalStructure.resetQuick();
}
for (int i = 0; i < sequence.size(); i++) {
this.originalSequence.add(sequence.getQuick(i));
this.originalStructure.add(structure.getQuick(i));
}
this.innerStructureMap.clear();
// init only if needed
if (this.backbone != null) {
this.backbone.resetQuick();
this.stemLoopSequence.resetQuick();
this.danglingLeft.resetQuick();
this.danglingRight.resetQuick();
} else {
this.backbone = new TCharArrayList();
this.stemLoopSequence = new TCharArrayList();
this.danglingLeft = new TCharArrayList();
this.danglingRight = new TCharArrayList();
}
// get the root information
final int rootStart = structure.lastIndexOf('(') + 1;
final int rootEnd = structure.indexOf(rootStart, ')') - 1;
final int loopSize = rootEnd - rootStart + 1;
// create new root only if needed
for (int i = 0; i < loopSize; i++) {
stemLoopSequence.add(sequence.get(i + rootStart));
}
// get the backbone and inner structures
int leftIndex = rootStart - 1;
int rightIndex = rootEnd + 1;
final int endDanglingLeft = structure.indexOf('(');
final int startDanglingRight = structure.lastIndexOf(')') + 1;
while (leftIndex >= endDanglingLeft && rightIndex <= startDanglingRight) {
// if both are a pair - into backbone
char leftChar = '(';
char rightChar = ')';
try {
leftChar = structure.get(leftIndex);
rightChar = structure.get(rightIndex);
} catch (Throwable e) {
System.out.println(sequence);
System.out.println(structure);
}
if (leftChar == '(' && rightChar == ')') {
this.backbone.add(structureAlphabet.map(sequence.get(leftIndex), sequence.get(rightIndex)));
leftIndex--;
rightIndex++;
} else if (leftChar == '.' && rightChar == '.') {
// both are dots - inner loop
InnerStructure in = allocateInnerStructure();
while (structure.get(leftIndex) != '(') {
in.appeandLeft(sequence.get(leftIndex));
leftIndex--;
}
while (structure.get(rightIndex) != ')') {
in.appeandRight(sequence.get(rightIndex));
rightIndex++;
}
innerStructureMap.put(backbone.size(), in);
this.backbone.add(StemStructureAlphabet.INNER_LOOP);
} else if (leftChar == '.') {
// left bulge
InnerStructure in = allocateInnerStructure();
while (structure.get(leftIndex) != '(') {
in.appeandLeft(sequence.get(leftIndex));
leftIndex--;
}
innerStructureMap.put(backbone.size(), in);
this.backbone.add(StemStructureAlphabet.BULDGE_LEFT);
} else {
// right bulge
InnerStructure in = allocateInnerStructure();
while (structure.get(rightIndex) != ')') {
in.appeandRight(sequence.get(rightIndex));
rightIndex++;
}
innerStructureMap.put(backbone.size(), in);
this.backbone.add(StemStructureAlphabet.BULDGE_RIGHT);
}
}
// handle the dangling edges
for (int i = 0; i < endDanglingLeft; i++) {
danglingLeft.add(sequence.get(i));
}
final int danglingRightLength = sequence.size() - startDanglingRight;
for (int i = 0; i < danglingRightLength; i++) {
danglingRight.add(sequence.get(startDanglingRight + i));
}
}
public void reuse(CharBuffer sequence, CharBuffer structure) {
reuse(null, sequence, structure);
}
public void reuse(String header, CharBuffer sequence, CharBuffer structure) {
// clear the current map to the cache
for (int i = 0; i < this.backbone.size(); i++) {
if (structureAlphabet.isSpecial(this.backbone.get(i))) {
innerStructurePool.enqueue(innerStructureMap.get(i));
}
}
innerStructureMap.clear();
init(header, charBuffer2List(sequence), charBuffer2List(structure));
}
private InnerStructure allocateInnerStructure() {
InnerStructure innerStructure = innerStructurePool.dequeue();
if (innerStructure == null) {
innerStructure = new InnerStructure();
} else {
innerStructure.reset();
}
return innerStructure;
}
public InnerStructure getInnerStructure(int position) {
return this.innerStructureMap.get(position);
}
public TCharArrayList getStemLoop() {
return stemLoopSequence;
}
public TCharArrayList getBackbone() {
return backbone;
}
/**
* check if the stem can be shuffled
*
* @return return if the stem can be shuffled
*/
public boolean isShuffleAllowed() {
return this.shuffleInformation != null;
}
/**
* Remove shuffle capabiliity
*/
public void disallowShuffle() {
this.shuffleInformation = null;
}
/**
* Allow shuffle and initiate the appropoate information needed
*/
public void allowShuffle() {
this.shuffleInformation = new StemStructureShuffleData();
this.shuffleInformation.init();
}
/**
* Convert the current Stem to JSON format string
*
* @return the JSON string representation of the current structure
*/
public String toJSON() {
StringBuilder builder = new StringBuilder();
String[] data = this.toVienna();
builder.append("{\"header\":\"");
builder.append(this.header);
builder.append('\"');
builder.append(",\"sequence\":\"");
builder.append(data[0]);
builder.append("\",\"structure\":\"");
builder.append(data[1]);
builder.append("\"}");
return builder.toString();
}
/**
*
* This method return the Vienna format representation of the current information stored in the
* Stem Structure. this information is returned based on the <b>current</b> information and not
* the original information provided when built.
*
* @return An array of size 2, the first element is the sequence and the second the structure
*/
public String[] toVienna() {
StringBuilder sbLeftSequence = new StringBuilder();
StringBuilder sbRightSequence = new StringBuilder();
StringBuilder sbLeftStructure = new StringBuilder();
StringBuilder sbRightStructure = new StringBuilder();
for (int i = 0; i < this.backbone.size(); i++) {
if (structureAlphabet.isSpecial(this.backbone.get(i))) {
/*
* in case of a inner structure add the information according to each sequence
*/
InnerStructure in = innerStructureMap.get(i);
TCharArrayList left = in.getSequenceLeft();
for (int l = 0; l < left.size(); l++) {
sbLeftSequence.append(left.get(l));
sbLeftStructure.append('.');
}
TCharArrayList right = in.getSequenceRight();
for (int r = 0; r < right.size(); r++) {
sbRightSequence.append(right.get(r));
sbRightStructure.append('.');
}
} else {
/* in case of a pair add the information directly */
char[] pair = structureAlphabet.decodePair(this.backbone.get(i));
sbLeftSequence.append(pair[0]);
sbLeftStructure.append('(');
sbRightSequence.append(pair[1]);
sbRightStructure.append(')');
}
}
// build the sequence
StringBuilder sbSequence = new StringBuilder();
addToBuilder(danglingLeft, sbSequence);
sbLeftSequence.reverse();
sbSequence.append(sbLeftSequence);
addToBuilder(stemLoopSequence, sbSequence);
sbSequence.append(sbRightSequence);
addToBuilder(danglingRight, sbSequence);
StringBuilder sbStructure = new StringBuilder();
addToBuilderDots(danglingLeft, sbStructure);
sbLeftStructure.reverse();
sbStructure.append(sbLeftStructure);
addToBuilderDots(stemLoopSequence, sbStructure);
sbStructure.append(sbRightStructure);
addToBuilderDots(danglingRight, sbStructure);
return new String[] {sbSequence.toString(), sbStructure.toString()};
}
/**
* Help function for format conversion.
*
* @param list the characters to write in the builder
* @param sb the builder into which we write the information
*/
private void addToBuilder(TCharArrayList list, StringBuilder sb) {
for (int i = 0; i < list.size(); i++) {
sb.append(list.get(i));
}
}
/**
* Help function for format conversion. This method adds '.' as long as the given list size
*
* @param list the number of characters to write in the builder
* @param sb the builder into which we write the dots
*/
private void addToBuilderDots(TCharArrayList list, StringBuilder sb) {
for (int i = 0; i < list.size(); i++) {
sb.append('.');
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(originalSequence);
builder.append('\n');
builder.append(originalStructure);
return builder.toString();
}
/**
* @return the Header sequence provided
*/
public final String getHeader() {
return header;
}
/**
* Get the original sequence
*
* @return A representation of the original sequence
*/
public TCharArrayList getOriginalSequence() {
return originalSequence;
}
/**
* Get the original Structure
*
* @return A representation of the original structure
*/
public TCharArrayList getOriginalStructure() {
return originalStructure;
}
/**
* @return the dangling sequence on the left
*/
public TCharArrayList getDanglingLeft() {
return danglingLeft;
}
/**
* @return the dangling sequence on the right
*/
public TCharArrayList getDanglingRight() {
return danglingRight;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof StemStructure))
return false;
StemStructure other = (StemStructure) obj;
if (!backbone.equals(other.backbone)) {
return false;
}
if (header == null) {
if (other.header != null)
return false;
} else if (!header.equals(other.header))
return false;
if (innerStructureMap == null) {
if (other.innerStructureMap != null)
return false;
} else if (!innerStructureMap.equals(other.innerStructureMap))
return false;
if (originalSequence == null) {
if (other.originalSequence != null)
return false;
} else if (!originalSequence.equals(other.originalSequence))
return false;
if (!this.stemLoopSequence.equals(other.stemLoopSequence)) {
return false;
}
if (originalStructure == null) {
if (other.originalStructure != null)
return false;
} else if (!originalStructure.equals(other.originalStructure))
return false;
return true;
}
public String toSVG() {
if (100 != 1) {
throw new UnsupportedOperationException("Not implemented completly");
}
StringBuilder sb = new StringBuilder();
sb.append("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" height=\"100%\" width=\"100%\">\n");
int y = 100;
int startX = 10;
for (int i = 0; i < this.backbone.size(); i++) {
int pos = startX + i * 50;
char[] temp = structureAlphabet.decodePair(backbone.get(i));
String data = temp[0] + "|" + temp[1];
sb.append("<text id='backbone-" + i + "' x=\"" + pos + "\" y=\"" + y + "\" fill=\"red\">"
+ data + "</text>\n");
if (structureAlphabet.isSpecial(backbone.get(i))) {
// this should be fixed
InnerStructure in = this.innerStructureMap.get(i);
sb.append(i);
sb.append(" [label=\"");
sb.append(in.getSequenceLeft());
sb.append(':');
sb.append(in.getSequenceRight());
sb.append("\"];");
}
}
sb.append("</svg>");
return sb.toString();
}
public String getStringForPosition(int i) {
StringBuilder sb = new StringBuilder();
if (structureAlphabet.isSpecial(backbone.get(i))) {
InnerStructure temp = this.innerStructureMap.get(i);
sb.append(temp.getSequenceLeft());
sb.append(':');
sb.append(temp.getSequenceRight());
} else {
char[] temp = structureAlphabet.decodePair(backbone.get(i));
sb.append(temp[0]);
sb.append('|');
sb.append(temp[1]);
}
return sb.toString();
}
public static ArrayList<StemStructure> readFromFile(String filename) {
ArrayList<StemStructure> list = new ArrayList<StemStructure>();
try {
FileReader file = new FileReader(new File(filename));
BufferedReader reader = new BufferedReader(file);
String header = reader.readLine();
String line1, line2;
while (header != null) {
line1 = reader.readLine();
line2 = reader.readLine();
list.add(new StemStructure(header.substring(1), new CharBuffer(line1),
new CharBuffer(line2)));
header = reader.readLine();
}
reader.close();
} catch (Exception e) {
list = null;
}
return list;
}
class StemStructureShuffleData {
/**
* Hold the information on the amount of stackings in the <b>original</b>
* {@link #StemStructure()}. The order is from the loop sequence to the outside
*/
TIntArrayList stackingLengths;
/**
* Counts the number of characters in each sides of the original structure
*/
private ArrayList<IntPair> counters;
/**
* Count the number of un-paired bases in the structure. It is the sum of bases in the dangling
* ends + the loop sequence + the bulges sequences.
*/
private int sumOfChararcters;
public StemStructureShuffleData() {
stackingLengths = new TIntArrayList();
}
public void init() {
sumOfChararcters = 0;
// left counters
TIntArrayList loopCounters = new TIntArrayList();
// add dangling left counters
loopCounters.add(danglingLeft.size());
sumOfChararcters += danglingLeft.size();
for (int i = 0; i < backbone.size(); i++) {
if (structureAlphabet.isSpecial(backbone.get(i))) {
int val = innerStructureMap.get(i).getSequenceLeft().size();
loopCounters.add(val);
sumOfChararcters += val;
}
}
// add stem loop length
loopCounters.add(stemLoopSequence.size());
sumOfChararcters += stemLoopSequence.size();
// add dangling right counters
for (int i = 0; i < backbone.size(); i++) {
if (structureAlphabet.isSpecial(backbone.get(i))) {
int val = innerStructureMap.get(i).getSequenceRight().size();
loopCounters.add(val);
sumOfChararcters += val;
}
}
loopCounters.add(danglingRight.size());
sumOfChararcters += danglingRight.size();
// move the information to a list and sort by size
counters = new ArrayList<IntPair>();
for (int i = 0; i < loopCounters.size(); i++) {
counters.add(new IntPair(i, loopCounters.get(i)));
}
Collections.sort(counters, new Comparator<IntPair>() {
@Override
public int compare(IntPair o1, IntPair o2) {
return o2.getSecond() - o1.getSecond();
}
});
// count stackings
int countFromLastInnerStrcuture = 0;
for (int i = 0; i < backbone.size(); i++) {
if (structureAlphabet.isSpecial(backbone.get(i))) {
stackingLengths.add(countFromLastInnerStrcuture);
countFromLastInnerStrcuture = 0;
} else {
countFromLastInnerStrcuture++;
}
}
stackingLengths.add(countFromLastInnerStrcuture);
}
}
}