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