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 " + existingAtom.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}