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 05.03.2004 021 * @author Andreas Prlic 022 * 023 */ 024package org.biojava.nbio.structure; 025 026import org.biojava.nbio.structure.io.GroupToSDF; 027import org.biojava.nbio.structure.io.mmcif.ChemCompGroupFactory; 028import org.biojava.nbio.structure.io.mmcif.model.ChemComp; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import java.io.Serializable; 033import java.util.*; 034 035/** 036 * 037 * Generic Implementation of a Group interface. 038 * AminoAcidImpl and NucleotideImpl are closely related classes. 039 * @see AminoAcidImpl 040 * @see NucleotideImpl 041 * @author Andreas Prlic 042 * @author Horvath Tamas 043 * @version %I% %G% 044 * @since 1.4 045 */ 046public class HetatomImpl implements Group,Serializable { 047 048 private static final Logger logger = LoggerFactory.getLogger(HetatomImpl.class); 049 050 private static final long serialVersionUID = 4491470432023820382L; 051 052 /** 053 * The GroupType is HETATM 054 */ 055 public static final GroupType type = GroupType.HETATM ; 056 057 private Map<String, Object> properties ; 058 059 private long id; 060 061 /** stores if 3d coordinates are available. */ 062 protected boolean pdb_flag ; 063 064 /** 3 letter name of amino acid in pdb file. */ 065 protected String pdb_name ; 066 067 protected ResidueNumber residueNumber; 068 069 protected List<Atom> atoms ; 070 071 private Chain parent; 072 073 /** 074 * Behaviors for how to balance memory vs. performance. 075 * @author Andreas Prlic 076 */ 077 public static enum PerformanceBehavior { 078 079 /** use a built-in HashMap for faster access to memory, at the price of more memory consumption */ 080 BETTER_PERFORMANCE_MORE_MEMORY, 081 082 /** Try to minimize memory consumption, at the price of slower speed when accessing atoms by name */ 083 LESS_MEMORY_SLOWER_PERFORMANCE 084 085 } 086 087 public static PerformanceBehavior performanceBehavior=PerformanceBehavior.LESS_MEMORY_SLOWER_PERFORMANCE; 088 089 private Map<String,Atom> atomNameLookup; 090 091 protected ChemComp chemComp ; 092 093 private List<Group> altLocs; 094 095 /** 096 * Construct a Hetatom instance. 097 */ 098 public HetatomImpl() { 099 super(); 100 101 pdb_flag = false; 102 pdb_name = null ; 103 104 residueNumber = null; 105 atoms = new ArrayList<Atom>(); 106 properties = new HashMap<String,Object>(); 107 parent = null; 108 chemComp = null; 109 altLocs = null; 110 111 if ( performanceBehavior == PerformanceBehavior.BETTER_PERFORMANCE_MORE_MEMORY) 112 atomNameLookup = new HashMap<String,Atom>(); 113 else 114 atomNameLookup = null; 115 } 116 117 118 /** 119 * returns true or false, depending if this group has 3D coordinates or not. 120 * @return true if Group has 3D coordinates 121 */ 122 @Override 123 public boolean has3D() { 124 return pdb_flag; 125 } 126 127 /** flag if group has 3D data. 128 * 129 * @param flag true to set flag that this Group has 3D coordinates 130 */ 131 @Override 132 public void setPDBFlag(boolean flag){ 133 pdb_flag = flag ; 134 } 135 136 /** Set three character name of Group . 137 * 138 * @param s a String specifying the PDBName value 139 * @see #getPDBName 140 */ 141 @Override 142 public void setPDBName(String s) { 143 // hetatoms can have pdb_name length < 3. e.g. CU (see 1a4a position 1200 ) 144 //if (s.length() != 3) { 145 //throw new PDBParseException("amino acid name is not of length 3!"); 146 //} 147 if (s != null && s.equals("?")) logger.info("invalid pdbname: ?"); 148 pdb_name =s ; 149 150 } 151 152 /** 153 * Returns the PDBName. 154 * 155 * @return a String representing the PDBName value 156 * @see #setPDBName 157 */ 158 @Override 159 public String getPDBName() { return pdb_name;} 160 161 /** 162 * {@inheritDoc} 163 */ 164 @Override 165 public void addAtom(Atom atom){ 166 atom.setGroup(this); 167 atoms.add(atom); 168 if (atom.getCoords() != null){ 169 // we have got coordinates! 170 setPDBFlag(true); 171 } 172 173 if (atomNameLookup != null){ 174 175 Atom existingAtom = atomNameLookup.put(atom.getName(), atom); 176 177 // if an atom with same name is added to the group that has to be some kind of problem, 178 // we need to warn properly 179 if (existingAtom != null) { 180 String altLocStr = ""; 181 char altLoc = atom.getAltLoc(); 182 if (altLoc != ' ') altLocStr = "(alt loc '" + altLoc + "')"; 183 logger.warn("An atom with name " + atom.getName() + " " + altLocStr + " is already present in group: " + this.toString() + ". The atom with serial " + atom.getPDBserial() + " will be ignored in look-ups."); 184 } 185 } 186 }; 187 188 189 /** remove all atoms 190 * 191 */ 192 @Override 193 public void clearAtoms() { 194 atoms.clear(); 195 setPDBFlag(false); 196 if ( atomNameLookup != null) 197 atomNameLookup.clear(); 198 } 199 200 /** 201 * {@inheritDoc} 202 */ 203 @Override 204 public int size(){ return atoms.size(); } 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override 210 public List<Atom> getAtoms(){ 211 return atoms ; 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override 218 public void setAtoms(List<Atom> atoms) { 219 220 // important we are resetting atoms to a new list, we need to reset the lookup too! 221 if ( atomNameLookup != null) 222 atomNameLookup.clear(); 223 224 for (Atom a: atoms){ 225 a.setGroup(this); 226 if ( atomNameLookup != null) 227 atomNameLookup.put(a.getName(),a); 228 } 229 this.atoms = atoms; 230 if (!atoms.isEmpty()) { 231 pdb_flag = true; 232 } 233 234 } 235 236 /** 237 * {@inheritDoc} 238 */ 239 @Override 240 public Atom getAtom(String name) { 241 if ( atomNameLookup != null) 242 return atomNameLookup.get(name); 243 else { 244 /** This is the performance penalty we pay for NOT using the atomnameLookup in PerformanceBehaviour.LESS_MEMORY_SLOWER_PERFORMANCE 245 */ 246 247 for (Atom a : atoms) { 248 if (a.getName().equals(name)) { 249 return a; 250 } 251 } 252 return null; 253 } 254 } 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override 260 public Atom getAtom(int position) { 261 262 if ((position < 0)|| ( position >= atoms.size())) { 263 //throw new StructureException("No atom found at position "+position); 264 return null; 265 } 266 return atoms.get(position); 267 } 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override 273 public boolean hasAtom(String fullName) { 274 275 if ( atomNameLookup != null) { 276 Atom a = atomNameLookup.get(fullName.trim()); 277 return a != null; 278 } else { 279 /** This is the performance penalty we pay for NOT using the atomnameLookup in PerformanceBehaviour.LESS_MEMORY_SLOWER_PERFORMANCE 280 */ 281 for (Atom a : atoms) { 282 if (a.getName().equals(fullName)) { 283 return true; 284 } 285 } 286 return false; 287 288 289 } 290 291 } 292 293 /** 294 * {@inheritDoc} 295 */ 296 @Override 297 public GroupType getType(){ return type;} 298 299 @Override 300 public String toString(){ 301 302 String str = "Hetatom "+ residueNumber + " " + pdb_name + " "+ pdb_flag; 303 if (pdb_flag) { 304 str = str + " atoms: "+atoms.size(); 305 } 306 if ( altLocs != null) 307 str += " has altLocs :" + altLocs.size(); 308 309 310 return str ; 311 312 } 313 314 /** 315 * {@inheritDoc} 316 */ 317 @Override 318 public boolean hasAminoAtoms(){ 319 // if this method call is performed too often, it should become a 320 // private method and provide a flag for Group object ... 321 322 return hasAtom(StructureTools.CA_ATOM_NAME) && 323 hasAtom(StructureTools.C_ATOM_NAME) && 324 hasAtom(StructureTools.N_ATOM_NAME) && 325 hasAtom(StructureTools.O_ATOM_NAME); 326 327 } 328 329 330 /** 331 * {@inheritDoc} 332 */ 333 @Override 334 public void setProperties(Map<String,Object> props) { 335 properties = props ; 336 } 337 338 /** return properties. 339 * 340 * @return a HashMap object representing the properties value 341 * @see #setProperties 342 */ 343 @Override 344 public Map<String, Object> getProperties() { 345 return properties ; 346 } 347 348 /** set a single property . 349 * 350 * @see #getProperties 351 * @see #getProperty 352 */ 353 @Override 354 public void setProperty(String key, Object value){ 355 properties.put(key,value); 356 } 357 358 /** get a single property . 359 * @param key a String 360 * @return an Object 361 * @see #setProperty 362 * @see #setProperties 363 */ 364 @Override 365 public Object getProperty(String key){ 366 return properties.get(key); 367 } 368 369 370 /** return an AtomIterator. 371 * 372 * @return an Iterator object 373 */ 374 @Override 375 public Iterator<Atom> iterator() { 376 return new AtomIterator(this); 377 } 378 379 /** returns and identical copy of this Group object . 380 * @return and identical copy of this Group object 381 */ 382 @Override 383 public Object clone() { 384 385 HetatomImpl n = new HetatomImpl(); 386 n.setPDBFlag(has3D()); 387 n.setResidueNumber(residueNumber); 388 389 n.setPDBName(getPDBName()); 390 391 // copy the atoms 392 for (Atom atom1 : atoms) { 393 Atom atom = (Atom) atom1.clone(); 394 n.addAtom(atom); 395 atom.setGroup(n); 396 } 397 398 // copying the alt loc groups if present, otherwise they stay null 399 if (altLocs!=null) { 400 for (Group altLocGroup:this.altLocs) { 401 Group nAltLocGroup = (Group)altLocGroup.clone(); 402 n.addAltLoc(nAltLocGroup); 403 } 404 } 405 406 if (chemComp!=null) 407 n.setChemComp(chemComp); 408 409 return n; 410 } 411 412 /** the Hibernate database ID 413 * 414 * @return the id 415 */ 416 public long getId() { 417 return id; 418 } 419 420 /** the Hibernate database ID 421 * 422 * @param id the hibernate id 423 */ 424 public void setId(long id) { 425 this.id = id; 426 } 427 428 @Override 429 public ChemComp getChemComp() { 430 if ( chemComp == null ) { 431 chemComp = ChemCompGroupFactory.getChemComp(pdb_name); 432 if (chemComp == null) logger.info("getChemComp: " + pdb_name); 433 } 434 return chemComp; 435 } 436 437 @Override 438 public void setChemComp(ChemComp cc) { 439 chemComp = cc; 440 441 } 442 443 /** 444 * {@inheritDoc} 445 */ 446 @Override 447 public void setChain(Chain chain) { 448 this.parent = chain; 449 //TODO: setChain(), getChainId() and ResidueNumber.set/getChainId() are 450 //duplicating functionality at present and could give different values. 451 if (residueNumber != null) { 452 residueNumber.setChainId(chain.getChainID()); 453 } 454 455 } 456 457 /** 458 * {@inheritDoc} 459 */ 460 @Override 461 public Chain getChain() { 462 return parent; 463 } 464 465 /** 466 * {@inheritDoc} 467 */ 468 @Override 469 public String getChainId() { 470 if (parent == null) { 471 return ""; 472 } 473 return parent.getChainID(); 474 } 475 476 /** 477 * {@inheritDoc} 478 */ 479 @Override 480 public ResidueNumber getResidueNumber() { 481 482 return residueNumber; 483 } 484 485 486 @Override 487 public void setResidueNumber(ResidueNumber residueNumber) { 488 this.residueNumber = residueNumber; 489 } 490 491 @Override 492 public void setResidueNumber(String chainId, Integer resNum, Character iCode) { 493 this.residueNumber = new ResidueNumber(chainId, resNum, iCode); 494 } 495 496 @Override 497 public boolean hasAltLoc() { 498 if ( altLocs == null) 499 return false; 500 return !altLocs.isEmpty(); 501 } 502 503 @Override 504 public List<Group> getAltLocs() { 505 if ( altLocs == null) 506 return new ArrayList<Group>(); 507 return altLocs; 508 } 509 510 @Override 511 public Group getAltLocGroup(Character altLoc) { 512 513 Atom a = getAtom(0); 514 if ( a == null) { 515 return null; 516 } 517 518 // maybe the alt loc group in question is myself 519 if (a.getAltLoc().equals(altLoc)) { 520 return this; 521 } 522 523 if (altLocs == null || altLocs.isEmpty()) 524 return null; 525 526 for (Group group : altLocs) { 527 if (group.getAtoms().isEmpty()) 528 continue; 529 530 // determine this group's alt-loc character code by looking 531 // at its first atom's alt-loc character 532 Atom b = group.getAtom(0); 533 if ( b == null) 534 continue; 535 536 if (b.getAltLoc().equals(altLoc)) { 537 return group; 538 } 539 } 540 541 return null; 542 } 543 544 @Override 545 public void addAltLoc(Group group) { 546 if ( altLocs == null) { 547 altLocs = new ArrayList<Group>(); 548 } 549 altLocs.add(group); 550 551 } 552 553 @Override 554 public boolean isWater() { 555 return GroupType.WATERNAMES.contains(pdb_name); 556 } 557 558 /** attempts to reduce the memory imprint of this group by trimming 559 * all internal Collection objects to the required size. 560 * 561 */ 562 @Override 563 public void trimToSize(){ 564 565 if ( atoms instanceof ArrayList<?>) { 566 ArrayList<Atom> myatoms = (ArrayList<Atom>) atoms; 567 myatoms.trimToSize(); 568 } 569 if ( altLocs instanceof ArrayList<?>){ 570 ArrayList<Group> myAltLocs = (ArrayList<Group>) altLocs; 571 myAltLocs.trimToSize(); 572 } 573 574 if ( hasAltLoc()) { 575 for (Group alt : getAltLocs()){ 576 alt.trimToSize(); 577 } 578 } 579 580 // now let's fit the hashmaps to size 581 properties = new HashMap<String, Object>(properties); 582 583 if ( atomNameLookup != null) 584 atomNameLookup = new HashMap<String,Atom>(atomNameLookup); 585 586 } 587 588 589 @Override 590 public String toSDF() { 591 // Function to return the SDF of a given strucutre 592 GroupToSDF gts = new GroupToSDF(); 593 return gts.getText(this); 594 } 595 596 597}