Skip to content
Snippets Groups Projects
Commit 994a828e authored by Piotr Gawron's avatar Piotr Gawron
Browse files

BindingRegion data model

parent 2f93cf65
No related branches found
No related tags found
1 merge request!345Resolve "Genes annotations don't show"
Showing
with 436 additions and 8 deletions
package lcsb.mapviewer.converter.model.celldesigner.species;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
......@@ -14,7 +17,9 @@ import lcsb.mapviewer.converter.model.celldesigner.structure.CellDesignerProtein
import lcsb.mapviewer.converter.model.celldesigner.structure.fields.CellDesignerModificationResidue;
import lcsb.mapviewer.converter.model.celldesigner.structure.fields.ModificationType;
import lcsb.mapviewer.model.map.species.Protein;
import lcsb.mapviewer.model.map.species.field.BindingRegion;
import lcsb.mapviewer.model.map.species.field.ModificationResidue;
import lcsb.mapviewer.model.map.species.field.Residue;
/**
* Class that performs parsing of the CellDesigner xml for
......@@ -80,6 +85,19 @@ public class ProteinXmlParser extends AbstractElementXmlParser<CellDesignerProte
}
}
}
} else if (node.getNodeName().equals("celldesigner:listOfBindingRegions")) {
NodeList residueList = node.getChildNodes();
for (int j = 0; j < residueList.getLength(); j++) {
Node residueNode = residueList.item(j);
if (residueNode.getNodeType() == Node.ELEMENT_NODE) {
if (residueNode.getNodeName().equalsIgnoreCase("celldesigner:bindingRegion")) {
protein.addModificationResidue(getBindingRegion(residueNode));
} else {
throw new InvalidXmlSchemaException(
"Unknown element of celldesigner:listOfModificationResidues " + residueNode.getNodeName());
}
}
}
} else if (node.getNodeName().equals("celldesigner:notes")) {
protein.setNotes(getRap().getNotes(node));
} else {
......@@ -108,13 +126,32 @@ public class ProteinXmlParser extends AbstractElementXmlParser<CellDesignerProte
}
attributes += " type=\"" + type + "\"";
result += "<celldesigner:protein" + attributes + ">\n";
if (protein.getModificationResidues().size() > 0) {
List<Residue> residues = new ArrayList<>();
List<BindingRegion> bindingRegions = new ArrayList<>();
for (ModificationResidue mr : protein.getModificationResidues()) {
if (mr instanceof Residue) {
residues.add((Residue) mr);
} else if (mr instanceof BindingRegion) {
bindingRegions.add((BindingRegion) mr);
} else {
throw new InvalidArgumentException("Don't know how to handle: " + mr.getClass());
}
}
if (residues.size() > 0) {
result += "<celldesigner:listOfModificationResidues>";
for (ModificationResidue mr : protein.getModificationResidues()) {
for (Residue mr : residues) {
result += toXml(mr);
}
result += "</celldesigner:listOfModificationResidues>\n";
}
if (bindingRegions.size() > 0) {
result += "<celldesigner:listOfBindingRegions>";
for (BindingRegion mr : bindingRegions) {
result += toXml(mr);
}
result += "</celldesigner:listOfBindingRegions>\n";
}
// ignore notes - all notes will be stored in species
// result +=
// "<celldesigner:notes>"+protein.getNotes()+"</celldesigner:notes>";
......@@ -123,13 +160,13 @@ public class ProteinXmlParser extends AbstractElementXmlParser<CellDesignerProte
}
/**
* Generates CellDesigner xml for {@link CellDesignerModificationResidue}.
* Generates CellDesigner xml for {@link Residue}.
*
* @param mr
* object to transform into xml
* @return CellDesigner xml for {@link CellDesignerModificationResidue}
* @return CellDesigner xml for {@link Residue}
*/
private String toXml(ModificationResidue mr) {
private String toXml(Residue mr) {
CellDesignerAliasConverter converter = new CellDesignerAliasConverter(mr.getSpecies(), false);
String result = "";
......@@ -147,6 +184,32 @@ public class ProteinXmlParser extends AbstractElementXmlParser<CellDesignerProte
return result;
}
/**
* Generates CellDesigner xml for {@link BindingRegion}.
*
* @param mr
* object to transform into xml
* @return CellDesigner xml for {@link BindingRegion}
*/
private String toXml(BindingRegion mr) {
CellDesignerModificationResidue cellDesignerModificationResidue = new CellDesignerModificationResidue(mr);
String result = "";
String attributes = "";
if (!mr.getIdModificationResidue().equals("")) {
attributes += " id=\"" + mr.getIdModificationResidue() + "\"";
}
if (!mr.getName().equals("")) {
attributes += " name=\"" + escapeXml(mr.getName()) + "\"";
}
attributes += " angle=\"" + cellDesignerModificationResidue.getAngle() + "\"";
attributes += " size=\"" + cellDesignerModificationResidue.getSize() + "\"";
result += "<celldesigner:bindingRegion " + attributes + ">";
result += "</celldesigner:bindingRegion>";
return result;
}
/**
* Parses CellDesigner xml node for ModificationResidue.
*
......@@ -174,4 +237,31 @@ public class ProteinXmlParser extends AbstractElementXmlParser<CellDesignerProte
return residue;
}
/**
* Parses CellDesigner xml node for ModificationResidue.
*
* @param residueNode
* xml node to parse
* @return {@link CellDesignerModificationResidue} object created from the node
* @throws InvalidXmlSchemaException
* thrown when input xml node doesn't follow defined schema
*/
private CellDesignerModificationResidue getBindingRegion(Node residueNode) throws InvalidXmlSchemaException {
CellDesignerModificationResidue residue = new CellDesignerModificationResidue();
residue.setIdModificationResidue(getNodeAttr("id", residueNode));
residue.setName(getNodeAttr("name", residueNode));
residue.setSize(getNodeAttr("size", residueNode));
residue.setAngle(getNodeAttr("angle", residueNode));
residue.setModificationType(ModificationType.BINDING_REGION);
NodeList list = residueNode.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
throw new InvalidXmlSchemaException(
"Unknown element of celldesigner:modificationResidue " + node.getNodeName());
}
}
return residue;
}
}
......@@ -4,11 +4,13 @@ import java.io.Serializable;
import org.apache.log4j.Logger;
import lcsb.mapviewer.common.Configuration;
import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.common.exception.NotImplementedException;
import lcsb.mapviewer.converter.model.celldesigner.geometry.CellDesignerAliasConverter;
import lcsb.mapviewer.converter.model.celldesigner.structure.CellDesignerSpecies;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.field.BindingRegion;
import lcsb.mapviewer.model.map.species.field.CodingRegion;
import lcsb.mapviewer.model.map.species.field.ModificationResidue;
import lcsb.mapviewer.model.map.species.field.ModificationSite;
......@@ -123,6 +125,16 @@ public class CellDesignerModificationResidue implements Serializable {
} else if (mr instanceof ProteinBindingDomain) {
this.size = ((CodingRegion) mr).getWidth() / mr.getSpecies().getWidth();
this.modificationType = ModificationType.PROTEIN_BINDING_DOMAIN;
} else if (mr instanceof BindingRegion) {
this.modificationType = ModificationType.BINDING_REGION;
if (Math.abs(mr.getPosition().getX() - mr.getSpecies().getX()) < Configuration.EPSILON) {
this.size = ((BindingRegion) mr).getHeight() / mr.getSpecies().getHeight();
} else if (Math.abs(mr.getPosition().getX() - mr.getSpecies().getX() - mr.getSpecies().getWidth()) < Configuration.EPSILON) {
this.size = ((BindingRegion) mr).getHeight() / mr.getSpecies().getHeight();
} else {
this.size = ((BindingRegion) mr).getWidth() / mr.getSpecies().getWidth();
}
} else {
throw new InvalidArgumentException("Unknown modification type: " + mr.getClass());
}
......@@ -357,6 +369,8 @@ public class CellDesignerModificationResidue implements Serializable {
throw new InvalidArgumentException("No type information for modification: " + idModificationResidue);
} else if (modificationType.equals(ModificationType.RESIDUE)) {
return createResidue(element, converter);
} else if (modificationType.equals(ModificationType.BINDING_REGION)) {
return createBindingRegion(element, converter);
} else if (modificationType.equals(ModificationType.MODIFICATION_SITE)) {
return createModificationSite(element, converter);
} else if (modificationType.equals(ModificationType.CODING_REGION)) {
......@@ -378,6 +392,21 @@ public class CellDesignerModificationResidue implements Serializable {
return result;
}
private BindingRegion createBindingRegion(Element element, CellDesignerAliasConverter converter) {
BindingRegion result = new BindingRegion();
result.setIdModificationResidue(idModificationResidue);
result.setName(name);
result.setPosition(converter.getResidueCoordinates(element, angle));
if (Math.abs(result.getPosition().getX() - element.getX()) < Configuration.EPSILON) {
result.setHeight(element.getHeight() * size);
} else if (Math.abs(result.getPosition().getX() - element.getX() - element.getWidth()) < Configuration.EPSILON) {
result.setHeight(element.getHeight() * size);
} else {
result.setWidth(element.getWidth() * size);
}
return result;
}
private CodingRegion createCodingRegion(Element element, CellDesignerAliasConverter converter) {
CodingRegion result = new CodingRegion();
result.setWidth(element.getWidth() * size);
......
......@@ -3,6 +3,7 @@ package lcsb.mapviewer.converter.model.celldesigner.structure.fields;
import lcsb.mapviewer.common.exception.InvalidArgumentException;
public enum ModificationType {
BINDING_REGION(null), //
CODING_REGION("CodingRegion"), //
PROTEIN_BINDING_DOMAIN("proteinBindingDomain"), //
RESIDUE(null), //
......
......@@ -47,8 +47,11 @@ import lcsb.mapviewer.model.map.reaction.type.StateTransitionReaction;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.Gene;
import lcsb.mapviewer.model.map.species.GenericProtein;
import lcsb.mapviewer.model.map.species.Protein;
import lcsb.mapviewer.model.map.species.SimpleMolecule;
import lcsb.mapviewer.model.map.species.Species;
import lcsb.mapviewer.model.map.species.field.BindingRegion;
import lcsb.mapviewer.model.map.species.field.ModificationResidue;
public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
Logger logger = Logger.getLogger(CellDesignerXmlParserTest.class);
......@@ -1000,4 +1003,30 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
}
}
@Test
public void testBindingRegion() throws Exception {
try {
CellDesignerXmlParser parser = new CellDesignerXmlParser();
Model model = parser.createModel(new ConverterParams().filename("testFiles/protein_with_binding_region.xml"));
Protein protein = model.getElementByElementId("sa1");
assertEquals(1, protein.getModificationResidues().size());
ModificationResidue residue = protein.getModificationResidues().get(0);
assertTrue(residue instanceof BindingRegion);
BindingRegion bindingRegion = (BindingRegion) residue;
assertEquals(bindingRegion.getPosition().getX(), protein.getX(), Configuration.EPSILON);
assertTrue(bindingRegion.getWidth() < bindingRegion.getHeight());
String xml = parser.toXml(model);
InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
Model model2 = parser.createModel(new ConverterParams().inputStream(is));
model.setName(null);
ModelComparator comparator = new ModelComparator();
assertEquals(0, comparator.compare(model, model2));
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<sbml xmlns="http://www.sbml.org/sbml/level2/version4" xmlns:celldesigner="http://www.sbml.org/2001/ns/celldesigner" level="2" version="4">
<model metaid="untitled" id="untitled">
<annotation>
<celldesigner:extension>
<celldesigner:modelVersion>4.0</celldesigner:modelVersion>
<celldesigner:modelDisplay sizeX="600" sizeY="400"/>
<celldesigner:listOfCompartmentAliases/>
<celldesigner:listOfComplexSpeciesAliases/>
<celldesigner:listOfSpeciesAliases>
<celldesigner:speciesAlias id="sa2" species="s2">
<celldesigner:activity>inactive</celldesigner:activity>
<celldesigner:bounds x="192.0" y="114.5" w="189.0" h="42.0"/>
<celldesigner:font size="12"/>
<celldesigner:view state="usual"/>
<celldesigner:usualView>
<celldesigner:innerPosition x="0.0" y="0.0"/>
<celldesigner:boxSize width="189.0" height="42.0"/>
<celldesigner:singleLine width="1.0"/>
<celldesigner:paint color="ffffff66" scheme="Color"/>
</celldesigner:usualView>
<celldesigner:briefView>
<celldesigner:innerPosition x="0.0" y="0.0"/>
<celldesigner:boxSize width="80.0" height="60.0"/>
<celldesigner:singleLine width="0.0"/>
<celldesigner:paint color="3fff0000" scheme="Color"/>
</celldesigner:briefView>
<celldesigner:info state="empty" angle="-1.5707963267948966"/>
</celldesigner:speciesAlias>
</celldesigner:listOfSpeciesAliases>
<celldesigner:listOfGroups/>
<celldesigner:listOfProteins/>
<celldesigner:listOfGenes>
<celldesigner:gene id="gn1" name="s2" type="GENE">
<celldesigner:listOfRegions>
<celldesigner:region id="tr1" name="p1" size="0.0" pos="0.3" type="Modification Site" active="false"/>
<celldesigner:region id="tr2" name="p2" size="0.1" pos="0.41000000000000003" type="CodingRegion" active="false"/>
<celldesigner:region id="tr3" name="p3" size="0.1" pos="0.57" type="RegulatoryRegion" active="false"/>
<celldesigner:region id="tr4" name="p4" size="0.1" pos="0.75" type="transcriptionStartingSiteL" active="true"/>
<celldesigner:region id="tr5" name="p5" size="0.14" pos="0.8599999999999999" type="transcriptionStartingSiteR" active="false"/>
</celldesigner:listOfRegions>
</celldesigner:gene>
</celldesigner:listOfGenes>
<celldesigner:listOfRNAs/>
<celldesigner:listOfAntisenseRNAs/>
<celldesigner:listOfLayers/>
<celldesigner:listOfBlockDiagrams/>
</celldesigner:extension>
</annotation>
<listOfUnitDefinitions>
<unitDefinition metaid="substance" id="substance" name="substance">
<listOfUnits>
<unit metaid="CDMT00001" kind="mole"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="volume" id="volume" name="volume">
<listOfUnits>
<unit metaid="CDMT00002" kind="litre"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="area" id="area" name="area">
<listOfUnits>
<unit metaid="CDMT00003" kind="metre" exponent="2"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="length" id="length" name="length">
<listOfUnits>
<unit metaid="CDMT00004" kind="metre"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="time" id="time" name="time">
<listOfUnits>
<unit metaid="CDMT00005" kind="second"/>
</listOfUnits>
</unitDefinition>
</listOfUnitDefinitions>
<listOfCompartments>
<compartment metaid="default" id="default" size="1" units="volume"/>
</listOfCompartments>
<listOfSpecies>
<species metaid="s2" id="s2" name="s2" compartment="default" initialAmount="0">
<annotation>
<celldesigner:extension>
<celldesigner:positionToCompartment>inside</celldesigner:positionToCompartment>
<celldesigner:speciesIdentity>
<celldesigner:class>GENE</celldesigner:class>
<celldesigner:geneReference>gn1</celldesigner:geneReference>
</celldesigner:speciesIdentity>
</celldesigner:extension>
</annotation>
</species>
</listOfSpecies>
</model>
</sbml>
<?xml version="1.0" encoding="UTF-8"?>
<sbml xmlns="http://www.sbml.org/sbml/level2/version4" xmlns:celldesigner="http://www.sbml.org/2001/ns/celldesigner" level="2" version="4">
<model metaid="untitled" id="untitled">
<annotation>
<celldesigner:extension>
<celldesigner:modelVersion>4.0</celldesigner:modelVersion>
<celldesigner:modelDisplay sizeX="600" sizeY="400"/>
<celldesigner:listOfCompartmentAliases/>
<celldesigner:listOfComplexSpeciesAliases/>
<celldesigner:listOfSpeciesAliases>
<celldesigner:speciesAlias id="sa1" species="s1">
<celldesigner:activity>inactive</celldesigner:activity>
<celldesigner:bounds x="238.0" y="123.0" w="80.0" h="40.0"/>
<celldesigner:font size="12"/>
<celldesigner:view state="usual"/>
<celldesigner:usualView>
<celldesigner:innerPosition x="0.0" y="0.0"/>
<celldesigner:boxSize width="80.0" height="40.0"/>
<celldesigner:singleLine width="1.0"/>
<celldesigner:paint color="ffccffcc" scheme="Color"/>
</celldesigner:usualView>
<celldesigner:briefView>
<celldesigner:innerPosition x="0.0" y="0.0"/>
<celldesigner:boxSize width="80.0" height="60.0"/>
<celldesigner:singleLine width="0.0"/>
<celldesigner:paint color="3fff0000" scheme="Color"/>
</celldesigner:briefView>
<celldesigner:info state="empty" angle="-1.5707963267948966"/>
</celldesigner:speciesAlias>
</celldesigner:listOfSpeciesAliases>
<celldesigner:listOfGroups/>
<celldesigner:listOfProteins>
<celldesigner:protein id="pr1" name="s1" type="GENERIC">
<celldesigner:listOfBindingRegions>
<celldesigner:bindingRegion angle="3.141592653589793" id="rs1" name="test" size="0.42"/>
</celldesigner:listOfBindingRegions>
</celldesigner:protein>
</celldesigner:listOfProteins>
<celldesigner:listOfGenes/>
<celldesigner:listOfRNAs/>
<celldesigner:listOfAntisenseRNAs/>
<celldesigner:listOfLayers/>
<celldesigner:listOfBlockDiagrams/>
</celldesigner:extension>
</annotation>
<listOfUnitDefinitions>
<unitDefinition metaid="substance" id="substance" name="substance">
<listOfUnits>
<unit metaid="CDMT00001" kind="mole"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="volume" id="volume" name="volume">
<listOfUnits>
<unit metaid="CDMT00002" kind="litre"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="area" id="area" name="area">
<listOfUnits>
<unit metaid="CDMT00003" kind="metre" exponent="2"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="length" id="length" name="length">
<listOfUnits>
<unit metaid="CDMT00004" kind="metre"/>
</listOfUnits>
</unitDefinition>
<unitDefinition metaid="time" id="time" name="time">
<listOfUnits>
<unit metaid="CDMT00005" kind="second"/>
</listOfUnits>
</unitDefinition>
</listOfUnitDefinitions>
<listOfCompartments>
<compartment metaid="default" id="default" size="1" units="volume"/>
</listOfCompartments>
<listOfSpecies>
<species metaid="s1" id="s1" name="s1" compartment="default" initialAmount="0">
<annotation>
<celldesigner:extension>
<celldesigner:positionToCompartment>inside</celldesigner:positionToCompartment>
<celldesigner:speciesIdentity>
<celldesigner:class>PROTEIN</celldesigner:class>
<celldesigner:proteinReference>pr1</celldesigner:proteinReference>
</celldesigner:speciesIdentity>
</celldesigner:extension>
</annotation>
</species>
</listOfSpecies>
</model>
</sbml>
......@@ -28,6 +28,12 @@ public abstract class AbstractRegionModification extends ModificationResidue {
@Column(name = "width")
private double width = DEFAULT_SIZE;
/**
* Height of the region in the graphic representation.
*/
@Column(name = "height")
private double height = DEFAULT_SIZE;
public AbstractRegionModification() {
super();
}
......@@ -35,6 +41,7 @@ public abstract class AbstractRegionModification extends ModificationResidue {
public AbstractRegionModification(AbstractRegionModification mr) {
super(mr);
this.width = mr.getWidth();
this.height = mr.getHeight();
}
@Override
......@@ -46,7 +53,8 @@ public abstract class AbstractRegionModification extends ModificationResidue {
} else {
position = "Point2D[" + format.format(getPosition().getX()) + "," + format.format(getPosition().getY()) + "]";
}
String result = getIdModificationResidue() + "," + getName() + "," + getWidth() + "," + position;
String result = getIdModificationResidue() + "," + getName() + "," + getWidth() + "," + getHeight() + ","
+ position;
return result;
}
......@@ -70,9 +78,12 @@ public abstract class AbstractRegionModification extends ModificationResidue {
&& !this.getIdModificationResidue().equals(mr.getIdModificationResidue())) {
throw new InvalidArgumentException("Cannot update from mr with different id");
}
if (mr.getWidth() > 0) {
if (mr.getWidth() > 0 && mr.getWidth() != DEFAULT_SIZE) {
this.setWidth(mr.getWidth());
}
if (mr.getHeight() > 0 && mr.getHeight() != DEFAULT_SIZE) {
this.setHeight(mr.getHeight());
}
if (mr.getName() != null) {
this.setName(mr.getName());
}
......@@ -80,4 +91,12 @@ public abstract class AbstractRegionModification extends ModificationResidue {
this.setPosition(mr.getPosition());
}
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
\ No newline at end of file
package lcsb.mapviewer.model.map.species.field;
import java.io.Serializable;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import org.apache.log4j.Logger;
import lcsb.mapviewer.common.exception.NotImplementedException;
import lcsb.mapviewer.model.map.species.AntisenseRna;
import lcsb.mapviewer.model.map.species.Gene;
import lcsb.mapviewer.model.map.species.Rna;
import lcsb.mapviewer.model.map.species.Species;
/**
* This structure contains information about Coding Region for one of the
* following {@link Species}:
* <ul>
* <li>{@link Rna}</li>
* <li>{@link AntisenseRna}</li>
* <li>{@link Gene}</li>
* </ul>
*
* @author Piotr Gawron
*
*/
@Entity
@DiscriminatorValue("MODIFICATION_SITE")
public class BindingRegion extends AbstractRegionModification implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Default class logger.
*/
@SuppressWarnings("unused")
private static Logger logger = Logger.getLogger(BindingRegion.class);
/**
* Default constructor.
*/
public BindingRegion() {
}
/**
* Constructor that initialize object with the data from the parameter.
*
* @param original
* object from which we initialize data
*/
public BindingRegion(BindingRegion original) {
super(original);
}
/**
* Creates a copy of current object.
*
* @return copy of the object
*/
public BindingRegion copy() {
if (this.getClass() == BindingRegion.class) {
return new BindingRegion(this);
} else {
throw new NotImplementedException("Method copy() should be overriden in class " + this.getClass());
}
}
}
......@@ -55,7 +55,6 @@ public class CodingRegion extends AbstractRegionModification implements Serializ
*/
public CodingRegion(CodingRegion original) {
super(original);
this.setWidth(original.getWidth());
}
/**
......
-- modification residue should have height
alter table modification_residue_table add column height numeric(6,2);
update modification_residue_table set height = 10.0;
......@@ -176,6 +176,7 @@
<value>lcsb.mapviewer.model.map.species.TruncatedProtein</value>
<value>lcsb.mapviewer.model.map.species.Unknown</value>
<value>lcsb.mapviewer.model.map.species.field.BindingRegion</value>
<value>lcsb.mapviewer.model.map.species.field.CodingRegion</value>
<value>lcsb.mapviewer.model.map.species.field.ModificationSite</value>
<value>lcsb.mapviewer.model.map.species.field.ProteinBindingDomain</value>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment