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