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}