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