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}