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 //clone atoms and bonds. 457 cloneAtomsAndBonds(n); 458 459 // copying the alt loc groups if present, otherwise they stay null 460 if (altLocs!=null) { 461 for (Group altLocGroup:this.altLocs) { 462 Group nAltLocGroup = (Group)altLocGroup.clone(); 463 n.addAltLoc(nAltLocGroup); 464 } 465 } 466 467 if (chemComp!=null) 468 n.setChemComp(chemComp); 469 470 return n; 471 } 472 473 474 protected void cloneAtomsAndBonds(Group newGroup) { 475 // copy the atoms 476 for (Atom atom1 : atoms) { 477 Atom atom = (Atom) atom1.clone(); 478 newGroup.addAtom(atom); 479 atom.setGroup(newGroup); 480 } 481 // copy the bonds 482 for (int i=0;i<atoms.size();i++) { 483 Atom atom1 = atoms.get(i); 484 List<Bond> bonds1 = atom1.getBonds(); 485 if (bonds1 != null) { 486 for (Bond b : bonds1) { 487 int atomAIndex = atoms.indexOf(b.getAtomA()); 488 int atomBIndex = atoms.indexOf(b.getAtomB()); 489 // The order of the atoms are the same on the original and the cloned object, which we use here. 490 Bond newBond = new BondImpl(newGroup.getAtom(atomAIndex), newGroup.getAtom(atomBIndex), b.getBondOrder(), false); 491 newGroup.getAtom(i).addBond(newBond); 492 } 493 } 494 } 495 } 496 497 /** the Hibernate database ID 498 * 499 * @return the id 500 */ 501 public long getId() { 502 return id; 503 } 504 505 /** the Hibernate database ID 506 * 507 * @param id the hibernate id 508 */ 509 public void setId(long id) { 510 this.id = id; 511 } 512 513 @Override 514 public ChemComp getChemComp() { 515 if ( chemComp == null ) { 516 chemComp = ChemCompGroupFactory.getChemComp(pdb_name); 517 if (chemComp == null) logger.info("getChemComp: " + pdb_name); 518 } 519 return chemComp; 520 } 521 522 @Override 523 public void setChemComp(ChemComp cc) { 524 chemComp = cc; 525 526 } 527 528 /** 529 * {@inheritDoc} 530 */ 531 @Override 532 public void setChain(Chain chain) { 533 this.parent = chain; 534 //TODO: setChain(), getChainName() and ResidueNumber.set/getChainName() are 535 //duplicating functionality at present and could give different values. 536 if (residueNumber != null) { 537 residueNumber.setChainName(chain.getName()); 538 } 539 540 } 541 542 /** 543 * {@inheritDoc} 544 */ 545 @Override 546 public Chain getChain() { 547 return parent; 548 } 549 550 /** 551 * {@inheritDoc} 552 */ 553 @Override 554 public String getChainId() { 555 if (parent == null) { 556 return ""; 557 } 558 return parent.getId(); 559 } 560 561 /** 562 * {@inheritDoc} 563 */ 564 @Override 565 public ResidueNumber getResidueNumber() { 566 567 return residueNumber; 568 } 569 570 571 @Override 572 public void setResidueNumber(ResidueNumber residueNumber) { 573 this.residueNumber = residueNumber; 574 } 575 576 @Override 577 public void setResidueNumber(String chainId, Integer resNum, Character iCode) { 578 this.residueNumber = new ResidueNumber(chainId, resNum, iCode); 579 } 580 581 @Override 582 public boolean hasAltLoc() { 583 if ( altLocs == null) 584 return false; 585 return !altLocs.isEmpty(); 586 } 587 588 @Override 589 public List<Group> getAltLocs() { 590 if ( altLocs == null) 591 return new ArrayList<Group>(); 592 return altLocs; 593 } 594 595 @Override 596 public Group getAltLocGroup(Character altLoc) { 597 598 Atom a = getAtom(0); 599 if ( a == null) { 600 return null; 601 } 602 603 // maybe the alt loc group in question is myself 604 if (a.getAltLoc().equals(altLoc)) { 605 return this; 606 } 607 608 if (altLocs == null || altLocs.isEmpty()) 609 return null; 610 611 for (Group group : altLocs) { 612 if (group.getAtoms().isEmpty()) 613 continue; 614 615 // determine this group's alt-loc character code by looking 616 // at its first atom's alt-loc character 617 Atom b = group.getAtom(0); 618 if ( b == null) 619 continue; 620 621 if (b.getAltLoc().equals(altLoc)) { 622 return group; 623 } 624 } 625 626 return null; 627 } 628 629 @Override 630 public void addAltLoc(Group group) { 631 if ( altLocs == null) { 632 altLocs = new ArrayList<Group>(); 633 } 634 altLocs.add(group); 635 636 } 637 638 @Override 639 public boolean isWater() { 640 return GroupType.WATERNAMES.contains(pdb_name); 641 } 642 643 /** attempts to reduce the memory imprint of this group by trimming 644 * all internal Collection objects to the required size. 645 * 646 */ 647 @Override 648 public void trimToSize(){ 649 650 if ( atoms instanceof ArrayList<?>) { 651 ArrayList<Atom> myatoms = (ArrayList<Atom>) atoms; 652 myatoms.trimToSize(); 653 } 654 if ( altLocs instanceof ArrayList<?>){ 655 ArrayList<Group> myAltLocs = (ArrayList<Group>) altLocs; 656 myAltLocs.trimToSize(); 657 } 658 659 if ( hasAltLoc()) { 660 for (Group alt : getAltLocs()){ 661 alt.trimToSize(); 662 } 663 } 664 665 // now let's fit the hashmaps to size 666 properties = new HashMap<String, Object>(properties); 667 668 if ( atomNameLookup != null) 669 atomNameLookup = new HashMap<String,Atom>(atomNameLookup); 670 671 } 672 673 674 @Override 675 public String toSDF() { 676 // Function to return the SDF of a given strucutre 677 GroupToSDF gts = new GroupToSDF(); 678 return gts.getText(this); 679 } 680 681 @Override 682 public boolean isHetAtomInFile() { 683 return isHetAtomInFile; 684 } 685 686 @Override 687 public void setHetAtomInFile(boolean isHetAtomInFile) { 688 this.isHetAtomInFile = isHetAtomInFile; 689 } 690 691 692 693 694}