001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 * Created on Jun 1, 2010 021 * Author: Jianjiong Gao 022 * 023 */ 024 025package org.biojava.nbio.protmod.io; 026 027import org.biojava.nbio.protmod.*; 028import org.w3c.dom.Document; 029import org.w3c.dom.NamedNodeMap; 030import org.w3c.dom.Node; 031import org.w3c.dom.NodeList; 032import org.xml.sax.SAXException; 033 034import javax.xml.parsers.DocumentBuilder; 035import javax.xml.parsers.DocumentBuilderFactory; 036import javax.xml.parsers.ParserConfigurationException; 037import java.io.IOException; 038import java.io.InputStream; 039import java.util.*; 040 041/** 042 * 043 * @author Jianjiong Gao 044 * @since 3.0 045 */ 046public final class ProteinModificationXmlReader { 047 /** 048 * This is a utility class and thus cannot be instantialized. 049 */ 050 private ProteinModificationXmlReader() {} 051 052 /** 053 * Read protein modifications from XML file and register them. 054 * @param isXml {@link InputStream} of the XML file. 055 * @throws IOException if failed to read the XML file. 056 * @throws ParserConfigurationException if parse errors occur. 057 * @throws SAXException the {@link DocumentBuilder} cannot be created. 058 */ 059 public static void registerProteinModificationFromXml(InputStream isXml) 060 throws IOException, ParserConfigurationException, SAXException { 061 if (isXml==null) { 062 throw new IllegalArgumentException("Null argument."); 063 } 064 065 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 066 DocumentBuilder builder = factory.newDocumentBuilder(); 067 Document doc = builder.parse(isXml); 068 069 NodeList modNodes = doc.getElementsByTagName("Entry"); 070 int modSize = modNodes.getLength(); 071 List<Node> nodes; 072 for (int iMod=0; iMod<modSize; iMod++) { 073 Node modNode = modNodes.item(iMod); 074 Map<String,List<Node>> infoNodes = getChildNodes(modNode); 075 076 // ID 077 nodes = infoNodes.get("Id"); 078 if (nodes==null || nodes.size()!=1) { 079 throw new RuntimeException("Each modification must have exact " + 080 "one <Id> field."); 081 } 082 String id = nodes.get(0).getTextContent(); 083 084 // modification category 085 nodes = infoNodes.get("Category"); 086 if (nodes==null || nodes.size()!=1) { 087 throw new RuntimeException("Each modification must have exact " + 088 "one <Category> field. See Modification "+id+"."); 089 } 090 ModificationCategory cat = ModificationCategory.getByLabel( 091 nodes.get(0).getTextContent()); 092 if (cat==null) { 093 throw new RuntimeException(nodes.get(0).getTextContent()+ 094 " is not defined as an modification category." + 095 " See Modification "+id+"."); 096 } 097 098 // occurrence type 099 nodes = infoNodes.get("Occurrence"); 100 if (nodes==null || nodes.size()!=1) { 101 throw new RuntimeException("Each modification must have exact " + 102 "one <Occurrence> field. See Modification "+id+"."); 103 } 104 ModificationOccurrenceType occType = ModificationOccurrenceType 105 .getByLabel(nodes.get(0).getTextContent()); 106 if (occType==null) { 107 throw new RuntimeException(nodes.get(0).getTextContent()+ 108 " is not defined as an modification occurence type." + 109 " See Modification "+id+"."); 110 } 111 112 // condition 113 ModificationCondition condition = null; 114 { 115 nodes = infoNodes.get("Condition"); 116 if (nodes==null || nodes.size()!=1) { 117 throw new RuntimeException("Each modification must have exact " + 118 "one <Condition> field. See Modification "+id+"."); 119 } 120 121 Node compsNode = nodes.get(0); 122 123 // keep track of the labels of component indices 124 Map<String,Integer> mapLabelComp = new HashMap<String,Integer>(); 125 126 Map<String,List<Node>> compInfoNodes = getChildNodes(compsNode); 127 128 // components 129 List<Node> compNodes = compInfoNodes.get("Component"); 130 int sizeComp = compNodes.size(); 131 List<Component> comps = new ArrayList<Component>(sizeComp); 132 for (int iComp=0; iComp<sizeComp; iComp++) { 133 Node compNode = compNodes.get(iComp); 134 // comp label 135 NamedNodeMap compNodeAttrs = compNode.getAttributes(); 136 Node labelNode = compNodeAttrs.getNamedItem("component"); 137 if (labelNode==null) { 138 throw new RuntimeException("Each component must have a label." + 139 " See Modification "+id+"."); 140 } 141 String label = labelNode.getTextContent(); 142 143 if (mapLabelComp.containsKey(label)) { 144 throw new RuntimeException("Each component must have a unique label." + 145 " See Modification "+id+"."); 146 } 147 148 // comp PDBCC ID 149 Set<String> compIds = new HashSet<String>(); 150 List<Node> compIdNodes = getChildNodes(compNode).get("Id"); 151 if (compIdNodes!=null) { 152 for (Node compIdNode : compIdNodes) { 153 NamedNodeMap compIdNodeAttr = compIdNode.getAttributes(); 154 Node compIdSource = compIdNodeAttr.getNamedItem("source"); 155 if (compIdSource!=null && compIdSource.getTextContent().equals("PDBCC")) { 156 String strComps = compIdNode.getTextContent(); 157 if (strComps.isEmpty()) { 158 throw new RuntimeException("Empty component." + 159 " See Modification "+id+"."); 160 } 161 compIds.addAll(Arrays.asList(strComps.split(","))); 162 } 163 } 164 } 165 166 if (compIds.isEmpty()) { 167 throw new RuntimeException("Each component must have a PDBCC ID." + 168 " See Modification "+id+"."); 169 } 170 171 // terminal 172 boolean nTerminal = false; 173 boolean cTerminal = false; 174 List<Node> compTermNode = getChildNodes(compNode).get("Terminal"); 175 if (compTermNode!=null) { 176 if (compTermNode.size()!=1) { 177 throw new RuntimeException("Only one <Terminal> condition is allowed for " + 178 "each component. See Modification "+id+"."); 179 } 180 String nc = compTermNode.get(0).getTextContent(); 181 if (nc.equals("N")) { 182 nTerminal = true; 183 } else if (nc.equals("C")) { 184 cTerminal = true; 185 } else { 186 throw new RuntimeException("Only N or C is allowed for <Terminal>." + 187 " See Modification "+id+"."); 188 } 189 } 190 191 // register 192 Component comp = Component.of(compIds, nTerminal, cTerminal); 193 comps.add(comp); 194 mapLabelComp.put(label, comps.size()-1); 195 } 196 197 // bonds 198 List<Node> bondNodes = compInfoNodes.get("Bond"); 199 List<ModificationLinkage> linkages = null; 200 if (bondNodes!=null) { 201 int sizeBonds = bondNodes.size(); 202 linkages = new ArrayList<ModificationLinkage>(sizeBonds); 203 for (int iBond=0; iBond<sizeBonds; iBond++) { 204 Node bondNode = bondNodes.get(iBond); 205 Map<String,List<Node>> bondChildNodes = getChildNodes(bondNode); 206 if (bondChildNodes==null) { 207 throw new RuntimeException("Each bond must contain two atoms" + 208 " See Modification "+id+"."); 209 } 210 211 List<Node> atomNodes = bondChildNodes.get("Atom"); 212 if (atomNodes==null || atomNodes.size()!=2) { 213 throw new RuntimeException("Each bond must contain two atoms" + 214 " See Modification "+id+"."); 215 } 216 217 // atom 1 218 NamedNodeMap atomNodeAttrs = atomNodes.get(0).getAttributes(); 219 Node compNode = atomNodeAttrs.getNamedItem("component"); 220 if (compNode==null) { 221 throw new RuntimeException("Each atom must on a component." + 222 " See Modification "+id+"."); 223 } 224 String labelComp1 = compNode.getTextContent(); 225 int iComp1 = mapLabelComp.get(labelComp1); 226 227 Node labelNode = atomNodeAttrs.getNamedItem("atom"); 228 String labelAtom1 = labelNode==null?null:labelNode.getTextContent(); 229 230 String atom1 = atomNodes.get(0).getTextContent(); 231 if (atom1.isEmpty()) { 232 throw new RuntimeException("Each atom must have a name. Please use wildcard * if unknown." + 233 " See Modification "+id+"."); 234 } 235 List<String> potentialAtoms1 = Arrays.asList(atom1.split(",")); 236 237 // atom 2 238 atomNodeAttrs = atomNodes.get(1).getAttributes(); 239 compNode = atomNodeAttrs.getNamedItem("component"); 240 if (compNode==null) { 241 throw new RuntimeException("Each atom must on a component." + 242 " See Modification "+id+"."); 243 } 244 String labelComp2 = compNode.getTextContent(); 245 int iComp2 = mapLabelComp.get(labelComp2); 246 247 labelNode = atomNodeAttrs.getNamedItem("atom"); 248 String labelAtom2 = labelNode==null?null:labelNode.getTextContent(); 249 250 String atom2 = atomNodes.get(1).getTextContent(); 251 if (atom2.isEmpty()) { 252 throw new RuntimeException("Each atom must have a name. Please use wildcard * if unknown." + 253 " See Modification "+id+"."); 254 } 255 List<String> potentialAtoms2 = Arrays.asList(atom2.split(",")); 256 257 // add linkage 258 ModificationLinkage linkage = new ModificationLinkage(comps, 259 iComp1, potentialAtoms1, labelAtom1, 260 iComp2, potentialAtoms2, labelAtom2); 261 linkages.add(linkage); 262 } 263 } 264 265 condition = new ModificationConditionImpl(comps, linkages); 266 } // end of condition 267 268 ProteinModificationImpl.Builder modBuilder = 269 new ProteinModificationImpl.Builder(id, cat, occType, condition); 270 271 // description 272 nodes = infoNodes.get("Description"); 273 if (nodes!=null && !nodes.isEmpty()) { 274 modBuilder.setDescription(nodes.get(0).getTextContent()); 275 } 276 277 // cross references 278 nodes = infoNodes.get("CrossReference"); 279 if (nodes!=null) { 280 for (Node node:nodes) { 281 Map<String,List<Node>> xrefInfoNodes = getChildNodes(node); 282 283 // source 284 List<Node> xrefNode = xrefInfoNodes.get("Source"); 285 if (xrefNode==null || xrefNode.size()!=1) { 286 throw new RuntimeException("Error in XML file: " + 287 "a cross reference must contain exactly one <Source> field." + 288 " See Modification "+id+"."); 289 } 290 String xrefDb = xrefNode.get(0).getTextContent(); 291 292 // id 293 xrefNode = xrefInfoNodes.get("Id"); 294 if (xrefNode==null || xrefNode.size()!=1) { 295 throw new RuntimeException("Error in XML file: " + 296 "a cross reference must contain exactly one <Id> field." + 297 " See Modification "+id+"."); 298 } 299 String xrefId = xrefNode.get(0).getTextContent(); 300 301 // name 302 String xrefName = null; 303 xrefNode = xrefInfoNodes.get("Name"); 304 if (xrefNode!=null && !xrefNode.isEmpty()) { 305 xrefName = xrefNode.get(0).getTextContent(); 306 } 307 308 if (xrefDb.equals("PDBCC")) { 309 modBuilder.setPdbccId(xrefId).setPdbccName(xrefName); 310 } else if (xrefDb.equals("RESID")) { 311 modBuilder.setResidId(xrefId).setResidName(xrefName); 312 } else if (xrefDb.equals("PSI-MOD")) { 313 modBuilder.setPsimodId(xrefId).setPsimodName(xrefName); 314 } 315 } 316 } // end of cross references 317 318 // formula 319 nodes = infoNodes.get("Formula"); 320 if (nodes!=null && !nodes.isEmpty()) { 321 modBuilder.setFormula(nodes.get(0).getTextContent()); 322 } 323 324 // keywords 325 nodes = infoNodes.get("Keyword"); 326 if (nodes!=null && !nodes.isEmpty()) { 327 for (Node node : nodes) { 328 modBuilder.addKeyword(node.getTextContent()); 329 } 330 } 331 332 ProteinModificationRegistry.register(modBuilder.build()); 333 } 334 } 335 336 /** 337 * Utility method to group child nodes by their names. 338 * @param parent parent node. 339 * @return Map from name to child nodes. 340 */ 341 private static Map<String,List<Node>> getChildNodes(Node parent) { 342 if (parent==null) 343 return Collections.emptyMap(); 344 345 Map<String,List<Node>> children = new HashMap<String,List<Node>>(); 346 347 NodeList nodes = parent.getChildNodes(); 348 int nNodes = nodes.getLength(); 349 for (int i=0; i<nNodes; i++) { 350 Node node = nodes.item(i); 351 if (node.getNodeType()!=Node.ELEMENT_NODE) 352 continue; 353 354 String name = node.getNodeName(); 355 List<Node> namesakes = children.get(name); 356 if (namesakes==null) { 357 namesakes = new ArrayList<Node>(); 358 children.put(name, namesakes); 359 } 360 namesakes.add(node); 361 } 362 363 return children; 364 } 365}