package fr.orsay.lri.varna.models.templates; import java.awt.Point; import java.awt.geom.Point2D; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import fr.orsay.lri.varna.exceptions.ExceptionEdgeEndpointAlreadyConnected; import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax; import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate; import fr.orsay.lri.varna.exceptions.ExceptionXMLGeneration; import fr.orsay.lri.varna.exceptions.ExceptionXmlLoading; import fr.orsay.lri.varna.models.rna.RNA; import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint; import fr.orsay.lri.varna.models.treealign.Tree; /** * A model for RNA templates. * A template is a way to display an RNA secondary structure. * * @author Raphael Champeimont */ public class RNATemplate { /** * The list of template elements. */ private Collection<RNATemplateElement> elements = new ArrayList<RNATemplateElement>(); /** * Tells whether the template contains elements. */ public boolean isEmpty() { return elements.isEmpty(); } /** * The first endpoint (in sequence order) of the template. * If there are multiple connected components, the first elements of one * connected component will be returned. * If the template contains no elements, null is returned. * If there is a cycle, an arbitrary endpoint will be returned * (as it then does not make sense to define the first endpoint). * Time: O(n) */ public RNATemplateElement getFirst() { return getFirstEndPoint().getElement(); } /** * The first endpoint edge endpoint (in sequence order) of the template. * If there are multiple connected components, the first elements of one * connected component will be returned. * If the template contains no elements, null is returned. * If there is a cycle, an arbitrary endpoint will be returned * (as it then does not make sense to define the first endpoint). * Time: O(n) */ public EdgeEndPoint getFirstEndPoint() { if (elements.isEmpty()) { return null; } else { Set<EdgeEndPoint> knownEndPoints = new HashSet<EdgeEndPoint>(); EdgeEndPoint currentEndPoint = getAnyEndPoint(); while (true) { if (knownEndPoints.contains(currentEndPoint)) { // There is a cycle in the template, so we stop there // to avoid looping infinitely. return currentEndPoint; } knownEndPoints.add(currentEndPoint); EdgeEndPoint previousEndPoint = currentEndPoint.getPreviousEndPoint(); if (previousEndPoint == null) { return currentEndPoint; } else { currentEndPoint = previousEndPoint; } } } } /** * Return an arbitrary element of the template, * null if empty. * Time: O(1) */ public RNATemplateElement getAny() { if (elements.isEmpty()) { return null; } else { return elements.iterator().next(); } } /** * Return an arbitrary endpoint of the template, * null if empty. * Time: O(1) */ public EdgeEndPoint getAnyEndPoint() { if (isEmpty()) { return null; } else { return getAny().getIn1EndPoint(); } } /** * Variable containing "this", used by the internal class * to access this object. */ private final RNATemplate template = this; /** * To get an iterator of this class, use rnaIterator(). * See rnaIterator() for documentation. */ private class RNAIterator implements Iterator<RNATemplateElement> { private Iterator<EdgeEndPoint> iter = vertexIterator(); public boolean hasNext() { return iter.hasNext(); } public RNATemplateElement next() { if (! hasNext()) { throw (new NoSuchElementException()); } EdgeEndPoint currentEndPoint = iter.next(); switch (currentEndPoint.getPosition()) { // We skip "IN" endpoints, so that we don't return elements twice case IN1: case IN2: // We get the corresponding "OUT" endpoint currentEndPoint = iter.next(); break; } return currentEndPoint.getElement(); } public void remove() { throw (new UnsupportedOperationException()); } } /** * Iterates over the elements of the template, in the sequence order. * Helixes will be given twice. * Only one connected component will be iterated on. * Note that if there is a cycle, the iterator may return a infinite * number of elements. */ public Iterator<RNATemplateElement> rnaIterator() { return new RNAIterator(); } /** * Iterates over all elements (each endpoint is given only once) * in an arbitrary order. */ public Iterator<RNATemplateElement> classicIterator() { return elements.iterator(); } private class VertexIterator implements Iterator<EdgeEndPoint> { private EdgeEndPoint endpoint = getFirstEndPoint(); public boolean hasNext() { return (endpoint != null); } public EdgeEndPoint next() { if (endpoint == null) { throw (new NoSuchElementException()); } EdgeEndPoint currentEndPoint = endpoint; endpoint = currentEndPoint.getNextEndPoint(); return currentEndPoint; } public void remove() { throw (new UnsupportedOperationException()); } } /** * Iterates over the elements edge endpoints of the template, * in the sequence order. * Only one connected component will be iterated on. * Note that if there is a cycle, the iterator may return a infinite * number of elements. */ public Iterator<EdgeEndPoint> vertexIterator() { return new VertexIterator(); } private class MakeEdgeList { List<EdgeEndPoint> list = new LinkedList<EdgeEndPoint>(); private void addEdgeIfNecessary(EdgeEndPoint endPoint) { if (endPoint.isConnected()) { list.add(endPoint); } } public List<EdgeEndPoint> make() { for (RNATemplateElement element: elements) { if (element instanceof RNATemplateHelix) { RNATemplateHelix helix = (RNATemplateHelix) element; addEdgeIfNecessary(helix.getIn1()); addEdgeIfNecessary(helix.getIn2()); } else if (element instanceof RNATemplateUnpairedSequence) { RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) element; addEdgeIfNecessary(sequence.getIn()); } } return list; } } /** * Return over all edges in an arbitrary order. * For each edge, the first (5' side) endpoint will be given. */ public List<EdgeEndPoint> makeEdgeList() { MakeEdgeList listMaker = new MakeEdgeList(); return listMaker.make(); } private class RemovePseudoKnots { /** * The elements of the template as an array, in the order of the * RNA sequence. Note that helixes will appear twice, * and non-paired sequences don't appear. */ private ArrayList<RNATemplateHelix> helixesSeq; /** * For any i, * j = helixesStruct[i] is the index in helixesSeq * where the same helix also appears. * It means we have for all i: * helixesSeq[helixesStruct[i]] == helixesSeq[i] */ private ArrayList<Integer> helixesStruct; /** * The same as helixesStruct, but without the pseudoknots, * ie. helixesStructWithoutPseudoKnots[i] may be -1 * even though helixesStruct[i] != -1 . */ private int[] helixesStructWithoutPseudoKnots; private void initArrays() throws ExceptionInvalidRNATemplate { helixesSeq = new ArrayList<RNATemplateHelix>(); helixesStruct = new ArrayList<Integer>(); Map<RNATemplateHelix,Integer> knownHelixes = new Hashtable<RNATemplateHelix,Integer>(); Iterator<RNATemplateElement> iter = rnaIterator(); while (iter.hasNext()) { RNATemplateElement element = iter.next(); if (element instanceof RNATemplateHelix) { helixesSeq.add((RNATemplateHelix) element); int index = helixesSeq.size() - 1; if (knownHelixes.containsKey(element)) { // This is the second time we meet this helix. int otherOccurenceIndex = knownHelixes.get(element); helixesStruct.add(otherOccurenceIndex); if (helixesStruct.get(otherOccurenceIndex) != -1) { throw (new ExceptionInvalidRNATemplate("We met an helix 3 times. Is there a cycle?")); } // Now we know the partner of the other part of // the helix. helixesStruct.set(otherOccurenceIndex, index); } else { // This is the first time we meet this helix. // Remember its index knownHelixes.put((RNATemplateHelix) element, index); // For the moment we don't know where the other part // of the helix is, but this will be found later. helixesStruct.add(-1); } } } } /** * Tells whether there is a pseudoknot. * Adapted from RNAMLParser.isSelfCrossing() */ private boolean isSelfCrossing() { Stack<Point> intervals = new Stack<Point>(); intervals.add(new Point(0, helixesStruct.size() - 1)); while (!intervals.empty()) { Point p = intervals.pop(); if (p.x <= p.y) { if (helixesStruct.get(p.x) == -1) { intervals.push(new Point(p.x + 1, p.y)); } else { int i = p.x; int j = p.y; int k = helixesStruct.get(i); if ((k <= i) || (k > j)) { return true; } else { intervals.push(new Point(i + 1, k - 1)); intervals.push(new Point(k + 1, j)); } } } } return false; } /** * We compute helixesStructWithoutPseudoKnots * from helixesStruct by replacing values by -1 * for the helixes we cut (the bases are become non-paired). * We try to cut as few base pairs as possible. */ private void removePseudoKnotsAux() { if (!isSelfCrossing()) { helixesStructWithoutPseudoKnots = new int[helixesStruct.size()]; for (int i=0; i<helixesStructWithoutPseudoKnots.length; i++) { helixesStructWithoutPseudoKnots[i] = helixesStruct.get(i); } } else { // We need to get rid of pseudoknots // This code was adapted from RNAMLParser.planarize() int length = helixesStruct.size(); int[] result = new int[length]; for (int i = 0; i < result.length; i++) { result[i] = -1; } short[][] tab = new short[length][length]; short[][] backtrack = new short[length][length]; // On the diagonal we have intervals containing only // one endpoint. Therefore there can be no helix // (because an helix consists of 2 elements). for (int i = 0; i < result.length; i++) { // tab[i][j] = 0; backtrack[i][i] = -1; } for (int n = 1; n < length; n++) { for (int i = 0; i < length - n; i++) { int j = i + n; tab[i][j] = tab[i + 1][j]; backtrack[i][j] = -1; int k = helixesStruct.get(i); assert k != -1; if ((k <= j) && (i < k)) { int tmp = helixesSeq.get(i).getLength(); if (i + 1 <= k - 1) { tmp += tab[i + 1][k - 1]; } if (k + 1 <= j) { tmp += tab[k + 1][j]; } if (tmp > tab[i][j]) { tab[i][j] = (short) tmp; backtrack[i][j] = (short) k; } } } } // debug //RNATemplateTests.printShortMatrix(tab); Stack<Point> intervals = new Stack<Point>(); intervals.add(new Point(0, length - 1)); while (!intervals.empty()) { Point p = intervals.pop(); if (p.x <= p.y) { if (backtrack[p.x][p.y] == -1) { result[p.x] = -1; intervals.push(new Point(p.x + 1, p.y)); } else { int i = p.x; int j = p.y; int k = backtrack[i][j]; result[i] = k; result[k] = i; intervals.push(new Point(i + 1, k - 1)); intervals.push(new Point(k + 1, j)); } } } helixesStructWithoutPseudoKnots = result; } } private Set<RNATemplateHelix> makeSet() { Set<RNATemplateHelix> removedHelixes = new HashSet<RNATemplateHelix>(); for (int i=0; i<helixesStructWithoutPseudoKnots.length; i++) { if (helixesStructWithoutPseudoKnots[i] < 0) { removedHelixes.add(helixesSeq.get(i)); } } return removedHelixes; } public Set<RNATemplateHelix> removePseudoKnots() throws ExceptionInvalidRNATemplate { initArrays(); removePseudoKnotsAux(); // debug //printIntArrayList(helixesStruct); //printIntArray(helixesStructWithoutPseudoKnots); return makeSet(); } } private class ConvertToTree { private Set<RNATemplateHelix> removedHelixes; public ConvertToTree(Set<RNATemplateHelix> removedHelixes) { this.removedHelixes = removedHelixes; } private Iterator<RNATemplateElement> iter = template.rnaIterator(); private Set<RNATemplateHelix> knownHelixes = new HashSet<RNATemplateHelix>(); public Tree<RNANodeValueTemplate> convert() throws ExceptionInvalidRNATemplate { Tree<RNANodeValueTemplate> root = new Tree<RNANodeValueTemplate>(); // No value, this is a fake node because we need a root. root.setValue(null); makeChildren(root); return root; } private void makeChildren(Tree<RNANodeValueTemplate> father) throws ExceptionInvalidRNATemplate { List<Tree<RNANodeValueTemplate>> children = father.getChildren(); while (true) { try { RNATemplateElement element = iter.next(); if (element instanceof RNATemplateHelix) { RNATemplateHelix helix = (RNATemplateHelix) element; if (removedHelixes.contains(helix)) { // Helix was removed boolean firstPartOfHelix; if (knownHelixes.contains(helix)) { firstPartOfHelix = false; } else { knownHelixes.add(helix); firstPartOfHelix = true; } int helixLength = helix.getLength(); // Maybe we could allow helixes of length 0? // If we want to then this code can be changed in the future. if (helixLength < 1) { throw (new ExceptionInvalidRNATemplate("Helix length < 1")); } int firstPosition = firstPartOfHelix ? 0 : helixLength; int afterLastPosition = firstPartOfHelix ? helixLength : 2*helixLength; for (int i=firstPosition; i<afterLastPosition; i++) { RNANodeValueTemplateBrokenBasePair value = new RNANodeValueTemplateBrokenBasePair(); value.setHelix(helix); value.setPositionInHelix(i); Tree<RNANodeValueTemplate> child = new Tree<RNANodeValueTemplate>(); child.setValue(value); father.getChildren().add(child); } } else { // We have an non-removed helix if (knownHelixes.contains(helix)) { if ((! (father.getValue() instanceof RNANodeValueTemplateBasePair)) || ((RNANodeValueTemplateBasePair) father.getValue()).getHelix() != helix) { // We have already met this helix, so unless it is our father, // we have a pseudoknot (didn't we remove them???). throw (new ExceptionInvalidRNATemplate("Unexpected helix. Looks like there still are pseudoknots even after we removed them so something is wrong about the template.")); } else { // As we have found the father, we have finished our work // with the children. return; } } else { knownHelixes.add(helix); int helixLength = helix.getLength(); // Maybe we could allow helixes of length 0? // If we want to then this code can be changed in the future. if (helixLength < 1) { throw (new ExceptionInvalidRNATemplate("Helix length < 1")); } Tree<RNANodeValueTemplate> lastChild = father; for (int i=0; i<helixLength; i++) { RNANodeValueTemplateBasePair value = new RNANodeValueTemplateBasePair(); value.setHelix(helix); value.setPositionInHelix(i); Tree<RNANodeValueTemplate> child = new Tree<RNANodeValueTemplate>(); child.setValue(value); lastChild.getChildren().add(child); lastChild = child; } // Now we put what follows as children of lastChild makeChildren(lastChild); } } } else if (element instanceof RNATemplateUnpairedSequence) { RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) element; int seqLength = sequence.getLength(); // Maybe we could allow sequences of length 0? // If we want to then this code can be changed in the future. if (seqLength < 1) { throw (new ExceptionInvalidRNATemplate("Non-paired sequence length < 1")); } RNANodeValueTemplateSequence value = new RNANodeValueTemplateSequence(); value.setSequence(sequence); Tree<RNANodeValueTemplate> child = new Tree<RNANodeValueTemplate>(); child.setValue(value); children.add(child); } else { throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?")); } } catch (NoSuchElementException e) { // We are at the end of elements so if everything is ok // the father must be the root. if (father.getValue() == null) { return; // Work finished. } else { throw (new ExceptionInvalidRNATemplate("Unexpected end of template endpoint list.")); } } } } } /** * Tells whether the connected component to which endPoint belongs to * is cyclic. */ public boolean connectedComponentIsCyclic(EdgeEndPoint endPoint) { Set<EdgeEndPoint> knownEndPoints = new HashSet<EdgeEndPoint>(); EdgeEndPoint currentEndPoint = endPoint; while (true) { if (knownEndPoints.contains(currentEndPoint)) { return true; } knownEndPoints.add(currentEndPoint); EdgeEndPoint previousEndPoint = currentEndPoint.getPreviousEndPoint(); if (previousEndPoint == null) { return false; } else { currentEndPoint = previousEndPoint; } } } /** * Tells whether the template elements are all connected, ie. if the * graph (edge endpoints being vertices) is connected. */ public boolean isConnected() { if (isEmpty()) { return true; } // Count all vertices int n = 0; for (RNATemplateElement element: elements) { if (element instanceof RNATemplateHelix) { n += 4; } else if (element instanceof RNATemplateUnpairedSequence) { n += 2; } } // Now try reaching all vertices Set<EdgeEndPoint> knownEndPoints = new HashSet<EdgeEndPoint>(); EdgeEndPoint currentEndPoint = getFirstEndPoint(); while (true) { if (knownEndPoints.contains(currentEndPoint)) { // We are back to our original endpoint, so stop break; } knownEndPoints.add(currentEndPoint); EdgeEndPoint nextEndPoint = currentEndPoint.getNextEndPoint(); if (nextEndPoint == null) { break; } else { currentEndPoint = nextEndPoint; } } // The graph is connected iff the number of reached vertices // is equal to the total number of vertices. return (knownEndPoints.size() == n); } /** * Checks whether this template is a valid RNA template, ie. * it is non-empty, it does not contain a cycle and all elements are in one * connected component. */ public void checkIsValidTemplate() throws ExceptionInvalidRNATemplate { if (isEmpty()) { throw (new ExceptionInvalidRNATemplate("The template is empty.")); } if (! isConnected()) { throw (new ExceptionInvalidRNATemplate("The template is a non-connected graph.")); } // Now we know the graph is connected. if (connectedComponentIsCyclic(getAnyEndPoint())) { throw (new ExceptionInvalidRNATemplate("The template is cyclic.")); } } /** * Make a tree of the template. For this, we will remove pseudoknots, * taking care to remove as few base pair links as possible. * Requires the template to be valid and will check the validity * (will call checkIsValidTemplate()). * Calling this method will automatically call computeIn1Is(). */ public Tree<RNANodeValueTemplate> toTree() throws ExceptionInvalidRNATemplate { // Compute the helix in1Is fields. // We also rely on computeIn1Is() for checking the template validity. computeIn1Is(); // Remove pseudoknots RemovePseudoKnots pseudoKnotKiller = new RemovePseudoKnots(); Set<RNATemplateHelix> removedHelixes = pseudoKnotKiller.removePseudoKnots(); // Convert to tree ConvertToTree converter = new ConvertToTree(removedHelixes); return converter.convert(); } /** * Generate an RNA sequence that exactly matches the template. * Requires the template to be valid and will check the validity * (will call checkIsValidTemplate()). */ public RNA toRNA() throws ExceptionInvalidRNATemplate { // First, we check this is a valid template checkIsValidTemplate(); ArrayList<Integer> str = new ArrayList<Integer>(); Map<RNATemplateHelix, ArrayList<Integer>> helixes = new HashMap<RNATemplateHelix, ArrayList<Integer>>(); Iterator<RNATemplateElement> iter = rnaIterator(); while (iter.hasNext()) { RNATemplateElement element = iter.next(); if (element instanceof RNATemplateHelix) { RNATemplateHelix helix = (RNATemplateHelix) element; int n = helix.getLength(); if (helixes.containsKey(helix)) { int firstBase = str.size(); ArrayList<Integer> helixMembers = helixes.get(helix); for (int i = 0; i < n; i++) { int indexOfAssociatedBase = helixMembers.get(n-1-i); str.set(indexOfAssociatedBase, firstBase + i); str.add(indexOfAssociatedBase); } } else { int firstBase = str.size(); ArrayList<Integer> helixMembers = new ArrayList<Integer>(); for (int i = 0; i < n; i++) { // We don't known yet where the associated base is str.add(-1); helixMembers.add(firstBase + i); } helixes.put(helix, helixMembers); } } else if (element instanceof RNATemplateUnpairedSequence) { RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) element; int n = sequence.getLength(); for (int i=0; i<n; i++) { str.add(-1); } } else { throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?")); } } int[] strAsArray = RNATemplateAlign.intArrayFromList(str); String[] seqAsArray = new String[strAsArray.length]; Arrays.fill(seqAsArray, " "); RNA rna = new RNA(); try { rna.setRNA(seqAsArray, strAsArray); } catch (ExceptionFileFormatOrSyntax e) { throw (new RuntimeException("Bug in toRNA(): setRNA() threw an ExceptionFileFormatOrSyntax exception.")); } return rna; } private class ConvertToXml { private Map<RNATemplateElement, String> elementNames = new HashMap<RNATemplateElement, String>(); private Element connectionsXmlElement; private Document document; private void addConnectionIfNecessary(EdgeEndPoint endPoint) { if (endPoint != null && endPoint.isConnected()) { RNATemplateElement e1 = endPoint.getElement(); EdgeEndPointPosition p1 = endPoint.getPosition(); RNATemplateElement e2 = endPoint.getOtherElement(); EdgeEndPointPosition p2 = endPoint.getOtherEndPoint().getPosition(); Element xmlElement = document.createElement("edge"); { Element fromXmlElement = document.createElement("from"); fromXmlElement.setAttribute("endpoint", elementNames.get(e1)); fromXmlElement.setAttribute("position", p1.toString()); xmlElement.appendChild(fromXmlElement); } { Element toXmlElement = document.createElement("to"); toXmlElement.setAttribute("endpoint", elementNames.get(e2)); toXmlElement.setAttribute("position", p2.toString()); xmlElement.appendChild(toXmlElement); } connectionsXmlElement.appendChild(xmlElement); } } public Document toXMLDocument() throws ExceptionXMLGeneration, ExceptionInvalidRNATemplate { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.newDocument(); Element root = document.createElement("RNATemplate"); document.appendChild(root); Element elementsXmlElement = document.createElement("elements"); root.appendChild(elementsXmlElement); connectionsXmlElement = document.createElement("edges"); root.appendChild(connectionsXmlElement); // First pass, we create a mapping between java references and names (strings) { int nextHelix = 1; int nextNonPairedSequence = 1; for (RNATemplateElement templateElement: elements) { if (templateElement instanceof RNATemplateHelix) { RNATemplateHelix helix = (RNATemplateHelix) templateElement; if (! elementNames.containsKey(helix)) { elementNames.put(helix, "H ID " + nextHelix); nextHelix++; } } else if (templateElement instanceof RNATemplateUnpairedSequence) { RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) templateElement; if (! elementNames.containsKey(sequence)) { elementNames.put(sequence, "S ID " + nextNonPairedSequence); nextNonPairedSequence++; } } else { throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?")); } } } // Now we generate the XML document for (RNATemplateElement templateElement: elements) { String elementXmlName = elementNames.get(templateElement); Element xmlElement; if (templateElement instanceof RNATemplateHelix) { RNATemplateHelix helix = (RNATemplateHelix) templateElement; xmlElement = document.createElement("helix"); xmlElement.setAttribute("id", elementXmlName); xmlElement.setAttribute("length", Integer.toString(helix.getLength())); xmlElement.setAttribute("flipped", Boolean.toString(helix.isFlipped())); if (helix.hasCaption()) { xmlElement.setAttribute("caption", helix.getCaption()); } { Element startPositionXmlElement = document.createElement("startPosition"); startPositionXmlElement.setAttribute("x", Double.toString(helix.getStartPosition().x)); startPositionXmlElement.setAttribute("y", Double.toString(helix.getStartPosition().y)); xmlElement.appendChild(startPositionXmlElement); } { Element endPositionXmlElement = document.createElement("endPosition"); endPositionXmlElement.setAttribute("x", Double.toString(helix.getEndPosition().x)); endPositionXmlElement.setAttribute("y", Double.toString(helix.getEndPosition().y)); xmlElement.appendChild(endPositionXmlElement); } addConnectionIfNecessary(helix.getOut1()); addConnectionIfNecessary(helix.getOut2()); } else if (templateElement instanceof RNATemplateUnpairedSequence) { RNATemplateUnpairedSequence sequence = (RNATemplateUnpairedSequence) templateElement; xmlElement = document.createElement("sequence"); xmlElement.setAttribute("id", elementXmlName); xmlElement.setAttribute("length", Integer.toString(sequence.getLength())); { Element vertex5XmlElement = document.createElement("vertex5"); vertex5XmlElement.setAttribute("x", Double.toString(sequence.getVertex5().x)); vertex5XmlElement.setAttribute("y", Double.toString(sequence.getVertex5().y)); xmlElement.appendChild(vertex5XmlElement); } { Element vertex3XmlElement = document.createElement("vertex3"); vertex3XmlElement.setAttribute("x", Double.toString(sequence.getVertex3().x)); vertex3XmlElement.setAttribute("y", Double.toString(sequence.getVertex3().y)); xmlElement.appendChild(vertex3XmlElement); } { Element inTangentVectorXmlElement = document.createElement("inTangentVector"); inTangentVectorXmlElement.setAttribute("angle", Double.toString(sequence.getInTangentVectorAngle())); inTangentVectorXmlElement.setAttribute("length", Double.toString(sequence.getInTangentVectorLength())); xmlElement.appendChild(inTangentVectorXmlElement); } { Element outTangentVectorXmlElement = document.createElement("outTangentVector"); outTangentVectorXmlElement.setAttribute("angle", Double.toString(sequence.getOutTangentVectorAngle())); outTangentVectorXmlElement.setAttribute("length", Double.toString(sequence.getOutTangentVectorLength())); xmlElement.appendChild(outTangentVectorXmlElement); } addConnectionIfNecessary(sequence.getOut()); } else { throw (new ExceptionInvalidRNATemplate("We have an endpoint which is neither an helix nor a sequence. What is that?")); } elementsXmlElement.appendChild(xmlElement); } return document; } catch (ParserConfigurationException e) { throw (new ExceptionXMLGeneration("ParserConfigurationException: " + e.getMessage())); } } } public void toXMLFile(File file) throws ExceptionXMLGeneration, ExceptionInvalidRNATemplate { try { Document xmlDocument = toXMLDocument(); Source source = new DOMSource(xmlDocument); Result result = new StreamResult(file); Transformer transformer; transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.transform(source, result); } catch (TransformerConfigurationException e) { throw (new ExceptionXMLGeneration("TransformerConfigurationException: " + e.getMessage())); } catch (TransformerFactoryConfigurationError e) { throw (new ExceptionXMLGeneration("TransformerFactoryConfigurationError: " + e.getMessage())); } catch (TransformerException e) { throw (new ExceptionXMLGeneration("TransformerException: " + e.getMessage())); } } public Document toXMLDocument() throws ExceptionXMLGeneration, ExceptionInvalidRNATemplate { ConvertToXml converter = new ConvertToXml(); return converter.toXMLDocument(); } private class LoadFromXml { private Document xmlDocument; private Map<String, RNATemplateElement> elementNames = new HashMap<String, RNATemplateElement>(); public LoadFromXml(Document xmlDocument) { this.xmlDocument = xmlDocument; } private Point2D.Double pointFromXml(Element xmlPoint) { Point2D.Double point = new Point2D.Double(); point.x = Double.parseDouble(xmlPoint.getAttribute("x")); point.y = Double.parseDouble(xmlPoint.getAttribute("y")); return point; } private double vectorLengthFromXml(Element xmlVector) { return Double.parseDouble(xmlVector.getAttribute("length")); } private double vectorAngleFromXml(Element xmlVector) { return Double.parseDouble(xmlVector.getAttribute("angle")); } /** * Takes an element of the form: * <anything endpoint="an element id" position="a valid EdgeEndPointPosition"/> * and returns the corresponding EdgeEndPoint object. */ private EdgeEndPoint endPointFromXml(Element xmlEdgeEndPoint) throws ExceptionXmlLoading { String elementId = xmlEdgeEndPoint.getAttribute("endpoint"); if (elementId == null || elementId == "") throw (new ExceptionXmlLoading ("Missing endpoint attribute on " + xmlEdgeEndPoint)); String positionOnElement = xmlEdgeEndPoint.getAttribute("position"); if (positionOnElement == null || positionOnElement == "") throw (new ExceptionXmlLoading ("Missing position attribute on " + xmlEdgeEndPoint)); if (elementNames.containsKey(elementId)) { RNATemplateElement templateElement = elementNames.get(elementId); EdgeEndPointPosition relativePosition = EdgeEndPointPosition.valueOf(positionOnElement); if (relativePosition == null) throw (new ExceptionXmlLoading ("Could not compute relativePosition")); return templateElement.getEndPointFromPosition(relativePosition); } else { throw (new ExceptionXmlLoading("Edge is connected on unkown element: " + elementId)); } } private String connectErrMsg(EdgeEndPoint v1, EdgeEndPoint v2, String reason) { return "Error while connecting\n" + v1.toString() + " to\n" + v2.toString() + " because:\n" + reason; } private void connect(EdgeEndPoint v1, EdgeEndPoint v2) throws ExceptionXmlLoading { if (v1 == null || v2 == null) { throw (new ExceptionXmlLoading("Invalid edge: missing endpoint\n v1 = " + v1 + "\n v2 = " + v2)); } if (v2.isConnected()) { throw (new ExceptionXmlLoading(connectErrMsg(v1, v2, "Second vertex is already connected to " + v2.getOtherElement().toString()))); } if (v1.isConnected()) { throw (new ExceptionXmlLoading(connectErrMsg(v1, v2, "First vertex is already connected to " + v1.getOtherElement().toString()))); } try { v1.connectTo(v2); } catch (ExceptionEdgeEndpointAlreadyConnected e) { throw (new ExceptionXmlLoading("A vertex is on two edges at the same time: " + e.getMessage())); } catch (ExceptionInvalidRNATemplate e) { throw (new ExceptionXmlLoading("ExceptionInvalidRNATemplate: " + e.getMessage())); } } public void load() throws ExceptionXmlLoading { // First part: we load all elements from the XML tree Element xmlElements = (Element) xmlDocument.getElementsByTagName("elements").item(0); { NodeList xmlElementsChildren = xmlElements.getChildNodes(); for (int i=0; i<xmlElementsChildren.getLength(); i++) { Node xmlElementsChild = xmlElementsChildren.item(i); if (xmlElementsChild instanceof Element) { Element xmlTemplateElement = (Element) xmlElementsChild; String tagName = xmlTemplateElement.getTagName(); if (tagName == "helix") { RNATemplateHelix helix = new RNATemplateHelix(xmlTemplateElement.getAttribute("id")); helix.setFlipped(Boolean.parseBoolean(xmlTemplateElement.getAttribute("flipped"))); helix.setLength(Integer.parseInt(xmlTemplateElement.getAttribute("length"))); if (xmlTemplateElement.hasAttribute("caption")) { helix.setCaption(xmlTemplateElement.getAttribute("caption")); } elementNames.put(xmlTemplateElement.getAttribute("id"), helix); NodeList xmlHelixChildren = xmlTemplateElement.getChildNodes(); for (int j=0; j<xmlHelixChildren.getLength(); j++) { Node xmlHelixChild = xmlHelixChildren.item(j); if (xmlHelixChild instanceof Element) { Element xmlHelixChildElement = (Element) xmlHelixChild; String helixChildTagName = xmlHelixChildElement.getTagName(); if (helixChildTagName == "startPosition") { helix.setStartPosition(pointFromXml(xmlHelixChildElement)); } else if (helixChildTagName == "endPosition") { helix.setEndPosition(pointFromXml(xmlHelixChildElement)); } } } } else if (tagName == "sequence") { RNATemplateUnpairedSequence sequence = new RNATemplateUnpairedSequence(xmlTemplateElement.getAttribute("id")); sequence.setLength(Integer.parseInt(xmlTemplateElement.getAttribute("length"))); elementNames.put(xmlTemplateElement.getAttribute("id"), sequence); NodeList xmlSequenceChildren = xmlTemplateElement.getChildNodes(); for (int j=0; j<xmlSequenceChildren.getLength(); j++) { Node xmlSequenceChild = xmlSequenceChildren.item(j); if (xmlSequenceChild instanceof Element) { Element xmlSequenceChildElement = (Element) xmlSequenceChild; String sequenceChildTagName = xmlSequenceChildElement.getTagName(); if (sequenceChildTagName == "inTangentVector") { sequence.setInTangentVectorLength(vectorLengthFromXml(xmlSequenceChildElement)); sequence.setInTangentVectorAngle(vectorAngleFromXml(xmlSequenceChildElement)); } else if (sequenceChildTagName == "outTangentVector") { sequence.setOutTangentVectorLength(vectorLengthFromXml(xmlSequenceChildElement)); sequence.setOutTangentVectorAngle(vectorAngleFromXml(xmlSequenceChildElement)); } else if (sequenceChildTagName == "vertex5") { sequence.setVertex5(pointFromXml(xmlSequenceChildElement)); } else if (sequenceChildTagName == "vertex3") { sequence.setVertex3(pointFromXml(xmlSequenceChildElement)); } } } } } } } // Second part: We read the edges from the XML tree Element xmlEdges = (Element) xmlDocument.getElementsByTagName("edges").item(0); { NodeList xmlEdgesChildren = xmlEdges.getChildNodes(); for (int i=0; i<xmlEdgesChildren.getLength(); i++) { Node xmlEdgesChild = xmlEdgesChildren.item(i); if (xmlEdgesChild instanceof Element) { Element xmlTemplateEdge = (Element) xmlEdgesChild; if (xmlTemplateEdge.getTagName() == "edge") { EdgeEndPoint v1 = null, v2 = null; NodeList xmlEdgeChildren = xmlTemplateEdge.getChildNodes(); for (int j=0; j<xmlEdgeChildren.getLength(); j++) { Node xmlEdgeChild = xmlEdgeChildren.item(j); if (xmlEdgeChild instanceof Element) { Element xmlEdgeChildElement = (Element) xmlEdgeChild; String edgeChildTagName = xmlEdgeChildElement.getTagName(); if (edgeChildTagName == "from") { v1 = endPointFromXml(xmlEdgeChildElement); } else if (edgeChildTagName == "to") { v2 = endPointFromXml(xmlEdgeChildElement); } } } if (v1 == null) throw (new ExceptionXmlLoading("Invalid edge: missing \"from\" declaration")); if (v2 == null) throw (new ExceptionXmlLoading("Invalid edge: missing \"to\" declaration")); connect(v1, v2); } } } } } } public static RNATemplate fromXMLFile(File file) throws ExceptionXmlLoading { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setIgnoringElementContentWhitespace(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document xmlDocument = builder.parse(file); return fromXMLDocument(xmlDocument); } catch (ParserConfigurationException e) { throw (new ExceptionXmlLoading("ParserConfigurationException: " + e.getMessage())); } catch (SAXException e) { throw (new ExceptionXmlLoading("SAXException: " + e.getMessage())); } catch (IOException e) { throw (new ExceptionXmlLoading("IOException: " + e.getMessage())); } } public static RNATemplate fromXMLDocument(Document xmlDocument) throws ExceptionXmlLoading { RNATemplate template = new RNATemplate(); LoadFromXml loader = template.new LoadFromXml(xmlDocument); loader.load(); return template; } /** * For an helix, tells us whether IN1/OUT1 is the 5' strand * (the first strand we meet if we follow the RNA sequence) * or the 3' strand (the second we meet if we follow the RNA sequence). */ public enum In1Is { IN1_IS_5PRIME, IN1_IS_3PRIME } /** * For each helix, compute the in1Is field. * If helices connections are changed, the value may become obsolete, * so you need to call this method again before accessing the in1Is * fields if you have modified connections in the template. * Requires the template to be valid and will check the validity * (will call checkIsValidTemplate()). */ public void computeIn1Is() throws ExceptionInvalidRNATemplate { checkIsValidTemplate(); Iterator<EdgeEndPoint> iter = vertexIterator(); Set<RNATemplateHelix> knownHelices = new HashSet<RNATemplateHelix>(); while (iter.hasNext()) { EdgeEndPoint endPoint = iter.next(); RNATemplateElement templateElement = endPoint.getElement(); if (templateElement instanceof RNATemplateHelix) { RNATemplateHelix helix = (RNATemplateHelix) templateElement; if (! knownHelices.contains(helix)) { // first time we meet this helix switch (endPoint.getPosition()) { case IN1: case OUT1: helix.setIn1Is(In1Is.IN1_IS_5PRIME); break; case IN2: case OUT2: helix.setIn1Is(In1Is.IN1_IS_3PRIME); break; } knownHelices.add(helix); } } } } /** * Remove the element from the template. * The element is automatically disconnected from any other element. * Returns true if and only if the element was present in the template, * otherwise nothing was done. */ public boolean removeElement(RNATemplateElement element) throws ExceptionInvalidRNATemplate { if (elements.contains(element)) { element.disconnectFromAny(); elements.remove(element); return true; } else { return false; } } /** * Position of an endpoint on an endpoint. * Not all values make sense for any endpoint. * For an helix, all four make sense, but for a non-paired * sequence, only IN1 and OUT1 make sense. */ public enum EdgeEndPointPosition { IN1, IN2, OUT1, OUT2; } private static int NEXT_ID = 1; /** * An endpoint of an RNA template, * it can be an helix or a sequence of non-paired bases. * * You cannot create an object of this class directly, * use RNATemplateHelix or RNATemplateUnpairedSequence instead. * * @author Raphael Champeimont */ public abstract class RNATemplateElement { public int _id = NEXT_ID++; public String getName() {return "RNATemplate"+_id; } /** * This variable is just there so that "this" can be accessed by a name * from the internal class EdgeEndPoint. */ private final RNATemplateElement element = this; /** * When the endpoint is created, it is added to the list of elements * in this template. To remove it, call RNATemplate.removeElement(). */ public RNATemplateElement() { elements.add(this); } /** * Disconnect this endpoint from any other elements it may be connected to. */ public abstract void disconnectFromAny(); /** * Get the the IN endpoint in the case of a sequence * and the IN1 endpoint in the case of an helix. */ public abstract EdgeEndPoint getIn1EndPoint(); /** * Returns the template to which this endpoint belongs. */ public RNATemplate getParentTemplate() { return template; } /** * Provided endpoint is an endpoint of this endpoint, get the next * endpoint, either on this same endpoint, or or the connected endpoint. * Note that you should use the getNextEndPoint() method of the endpoint * itself directly. */ protected abstract EdgeEndPoint getNextEndPoint(EdgeEndPoint endpoint); /** * Provided endpoint is an endpoint of this endpoint, get the previous * endpoint, either on this same endpoint, or or the connected endpoint. * Note that you should use the getPreviousEndPoint() method of the endpoint * itself directly. */ protected abstract EdgeEndPoint getPreviousEndPoint(EdgeEndPoint endpoint); /** * An edge endpoint is where an edge can connect. */ public class EdgeEndPoint { private EdgeEndPoint() { } /** * Get the next endpoint. If this endpoint is an "in" endpoint, * returns the corresponding "out" endpoint. If this endpoint * is an "out" endpoint, return the connected endpoint if there is * one, otherwise return null. */ public EdgeEndPoint getNextEndPoint() { return element.getNextEndPoint(this); } /** * Same as getNextEndPoint(), but with the previous endpoint. */ public EdgeEndPoint getPreviousEndPoint() { return element.getPreviousEndPoint(this); } /** * Get the position on the endpoint where this endpoint is. */ public EdgeEndPointPosition getPosition() { return element.getPositionFromEndPoint(this); } private EdgeEndPoint otherEndPoint; /** * Returns the endpoint on which this edge endpoint is. */ public RNATemplateElement getElement() { return element; } /** * Returns the other endpoint of the edge. * Will be null if there is no edge connecter to this endpoint. */ public EdgeEndPoint getOtherEndPoint() { return otherEndPoint; } /** * Returns the endpoint at the other endpoint of the edge. * Will be null if there is no edge connecter to this endpoint. */ public RNATemplateElement getOtherElement() { return (otherEndPoint != null) ? otherEndPoint.getElement() : null; } /** * Disconnect this endpoint from the other, ie. delete the edge * between them. Note that this will modify both endpoints, and that * x.disconnect() is equivalent to x.getOtherEndPoint().disconnect(). * If this endpoint is not connected, does nothing. */ public void disconnect() { if (otherEndPoint != null) { otherEndPoint.otherEndPoint = null; otherEndPoint = null; } } /** * Tells whether this endpoint is connected with an edge to * an other endpoint. */ public boolean isConnected() { return (otherEndPoint != null); } /** * Create an edge between two edge endpoints. * This is a symmetric operation and it will modify both endpoints. * It means x.connectTo(y) is equivalent to y.connectTo(x). * The edge endpoint must be free (ie. not yet connected). * Also, elements connected together must belong to the same template. */ public void connectTo(EdgeEndPoint otherEndPoint) throws ExceptionEdgeEndpointAlreadyConnected, ExceptionInvalidRNATemplate { if (this.otherEndPoint != null || otherEndPoint.otherEndPoint != null) { throw (new ExceptionEdgeEndpointAlreadyConnected()); } if (template != otherEndPoint.getElement().getParentTemplate()) { throw (new ExceptionInvalidRNATemplate("Elements from different templates cannot be connected with each other.")); } this.otherEndPoint = otherEndPoint; otherEndPoint.otherEndPoint = this; } public String toString() { return "Edge endpoint on element " + element.toString() + " at position " + getPosition().toString(); } } /** * Get the EdgeEndPoint object corresponding to the the given * position on this endpoint. */ public abstract EdgeEndPoint getEndPointFromPosition(EdgeEndPointPosition position); /** * The inverse of getEndPointFromPosition. */ public abstract EdgeEndPointPosition getPositionFromEndPoint(EdgeEndPoint endPoint); /** * Connect the endpoint at position positionHere of this endpoint * to the otherEndPoint. */ public void connectTo( EdgeEndPointPosition positionHere, EdgeEndPoint otherEndPoint) throws ExceptionEdgeEndpointAlreadyConnected, ExceptionInvalidRNATemplate { EdgeEndPoint endPointHere = getEndPointFromPosition(positionHere); endPointHere.connectTo(otherEndPoint); } /** * Connect the endpoint at position positionHere of this endpoint * to the endpoint of otherElement at position positionOnOtherElement. * @throws ExceptionInvalidRNATemplate * @throws ExceptionEdgeEndpointAlreadyConnected, ExceptionEdgeEndpointAlreadyConnected */ public void connectTo( EdgeEndPointPosition positionHere, RNATemplateElement otherElement, EdgeEndPointPosition positionOnOtherElement) throws ExceptionEdgeEndpointAlreadyConnected, ExceptionEdgeEndpointAlreadyConnected, ExceptionInvalidRNATemplate { EdgeEndPoint otherEndPoint = otherElement.getEndPointFromPosition(positionOnOtherElement); connectTo(positionHere, otherEndPoint); } } /** * An helix in an RNA template. * * @author Raphael Champeimont */ public class RNATemplateHelix extends RNATemplateElement { /** * Number of base pairs in the helix. */ private int length; /** * Position of the helix start point, * ie. the middle in the line [x,y] where (x,y) * x is the base at the IN1 edge endpoint and * y is the base at the OUT2 edge endpoint. */ private Point2D.Double startPosition; /** * Position of the helix end point, * ie. the middle in the line [x,y] where (x,y) * x is the base at the OUT1 edge endpoint and * y is the base at the IN2 edge endpoint. */ private Point2D.Double endPosition; /** * Tells whether the helix is flipped. */ private boolean flipped = false; public boolean isFlipped() { return flipped; } public void setFlipped(boolean flipped) { this.flipped = flipped; } /** * For an helix, tells us whether IN1/OUT1 is the 5' strand * (the first strand we meet if we follow the RNA sequence) * or the 3' strand (the second we meet if we follow the RNA sequence). * This information cannot be known locally, we need the complete * template to compute it, see RNATemplate.computeIn1Is(). */ private In1Is in1Is = null; public In1Is getIn1Is() { return in1Is; } public void setIn1Is(In1Is in1Is) { this.in1Is = in1Is; } /** * A string displayed on the helix. */ private String caption = null; public String getCaption() { return caption; } public void setCaption(String caption) { this.caption = caption; } public boolean hasCaption() { return caption != null; } /** * If we go through all bases of the RNA from first to last, * we will pass twice through this helix. On time, we arrive * from in1, and leave by out2, and the other time we arrive from * in2 and leave by out2. * Whether we go through in1/out1 or in2/out2 the first time * is written in the in1Is field. */ private final EdgeEndPoint in1, out1, in2, out2; private String _name; public RNATemplateHelix(String name) { in1 = new EdgeEndPoint(); out1 = new EdgeEndPoint(); in2 = new EdgeEndPoint(); out2 = new EdgeEndPoint(); _name = name; } public String toString() { return "Helix @" + Integer.toHexString(hashCode()) + " len=" + length + " caption=" + caption; } public String getName() {return ""+_name; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public Point2D.Double getStartPosition() { return startPosition; } public void setStartPosition(Point2D.Double startPosition) { this.startPosition = startPosition; } public Point2D.Double getEndPosition() { return endPosition; } public void setEndPosition(Point2D.Double endPosition) { this.endPosition = endPosition; } public EdgeEndPoint getIn1() { return in1; } public EdgeEndPoint getOut1() { return out1; } public EdgeEndPoint getIn2() { return in2; } public EdgeEndPoint getOut2() { return out2; } public void disconnectFromAny() { getIn1().disconnect(); getIn2().disconnect(); getOut1().disconnect(); getOut2().disconnect(); } protected EdgeEndPoint getNextEndPoint(EdgeEndPoint endpoint) { if (endpoint == in1) { return out1; } else if (endpoint == in2) { return out2; } else { return endpoint.getOtherEndPoint(); } } protected EdgeEndPoint getPreviousEndPoint(EdgeEndPoint endpoint) { if (endpoint == out1) { return in1; } else if (endpoint == out2) { return in2; } else { return endpoint.getOtherEndPoint(); } } public EdgeEndPoint getIn1EndPoint() { return in1; } public EdgeEndPoint getEndPointFromPosition( EdgeEndPointPosition position) { switch (position) { case IN1: return getIn1(); case IN2: return getIn2(); case OUT1: return getOut1(); case OUT2: return getOut2(); default: return null; } } public EdgeEndPointPosition getPositionFromEndPoint( EdgeEndPoint endPoint) { if (endPoint == in1) { return EdgeEndPointPosition.IN1; } else if (endPoint == in2) { return EdgeEndPointPosition.IN2; } else if (endPoint == out1) { return EdgeEndPointPosition.OUT1; } else if (endPoint == out2) { return EdgeEndPointPosition.OUT2; } else { return null; } } } /** * A sequence of non-paired bases in an RNA template. * * @author Raphael Champeimont */ public class RNATemplateUnpairedSequence extends RNATemplateElement { /** * Number of (non-paired) bases. */ private int length; private static final double defaultTangentVectorAngle = Math.PI / 2; private static final double defaultTangentVectorLength = 100; /** * The sequence is drawn along a cubic Bezier curve. * The curve can be defined by 2 vectors, one for the start of the line * and the other for the end. They are the tangents to the line at * the beginning and the end of the line. * Each vector can be defined by its length and its absolute angle. * The angles are given in radians. */ private double inTangentVectorAngle = defaultTangentVectorAngle, inTangentVectorLength = defaultTangentVectorLength, outTangentVectorAngle = defaultTangentVectorAngle, outTangentVectorLength = defaultTangentVectorLength; /** * Position of the begginning (at the "in" endpoint) of the line. * It is only useful when the sequence is not yet connected to an helix. * (Otherwise we can deduce it from this helix position). */ private Point2D.Double vertex5; /** * Position of the end (at the "out" endpoint) of the line. * It is only useful when the sequence is not yet connected to an helix. * (Otherwise we can deduce it from this helix position). */ private Point2D.Double vertex3; public Point2D.Double getVertex5() { return vertex5; } public void setVertex5(Point2D.Double vertex5) { this.vertex5 = vertex5; } public Point2D.Double getVertex3() { return vertex3; } public void setVertex3(Point2D.Double vertex3) { this.vertex3 = vertex3; } /** * The helixes connected on both sides. * They must be helixes because only helixes have absolute positions, * and the positions of the starting and ending points of the sequence * are those stored in the helixes. */ private final EdgeEndPoint in, out; private String _name; public RNATemplateUnpairedSequence(String name) { in = new EdgeEndPoint(); out = new EdgeEndPoint(); _name = name; } public String toString() { return "Sequence @" + Integer.toHexString(hashCode()) + " len=" + length; } public String getName() {return ""+_name; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public double getInTangentVectorAngle() { return inTangentVectorAngle; } public void setInTangentVectorAngle(double inTangentVectorAngle) { this.inTangentVectorAngle = inTangentVectorAngle; } public double getInTangentVectorLength() { return inTangentVectorLength; } public void setInTangentVectorLength(double inTangentVectorLength) { this.inTangentVectorLength = inTangentVectorLength; } public double getOutTangentVectorAngle() { return outTangentVectorAngle; } public void setOutTangentVectorAngle(double outTangentVectorAngle) { this.outTangentVectorAngle = outTangentVectorAngle; } public double getOutTangentVectorLength() { return outTangentVectorLength; } public void setOutTangentVectorLength(double outTangentVectorLength) { this.outTangentVectorLength = outTangentVectorLength; } public EdgeEndPoint getIn() { return in; } public EdgeEndPoint getOut() { return out; } public void disconnectFromAny() { getIn().disconnect(); getOut().disconnect(); } protected EdgeEndPoint getNextEndPoint(EdgeEndPoint endpoint) { if (endpoint == in) { return out; } else { return endpoint.getOtherEndPoint(); } } protected EdgeEndPoint getPreviousEndPoint(EdgeEndPoint endpoint) { if (endpoint == out) { return in; } else { return endpoint.getOtherEndPoint(); } } public EdgeEndPoint getIn1EndPoint() { return in; } public EdgeEndPoint getEndPointFromPosition( EdgeEndPointPosition position) { switch (position) { case IN1: return getIn(); case OUT1: return getOut(); default: return null; } } public EdgeEndPointPosition getPositionFromEndPoint( EdgeEndPoint endPoint) { if (endPoint == in) { return EdgeEndPointPosition.IN1; } else if (endPoint == out) { return EdgeEndPointPosition.OUT1; } else { return null; } } } }