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 26.04.2004
021 * @author Andreas Prlic
022 *
023 */
024package org.biojava.nbio.structure;
025
026import java.io.Serializable;
027import java.util.ArrayList;
028import java.util.List;
029import java.util.ListIterator;
030import java.util.Map;
031
032import org.biojava.nbio.structure.io.CompoundFinder;
033import org.biojava.nbio.structure.io.FileConvert;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037/**
038 * Implementation of a PDB Structure. This class
039 * provides the data contained in a PDB file.
040 * to get structure objects from different sources
041 * see io package.
042 *
043 * @author Andreas Prlic
044 * @author Jules Jacobsen
045 * @since 1.4
046 * @version %I% %G%
047 */
048public class StructureImpl implements Structure, Serializable {
049
050        private static final long serialVersionUID = -8344837138032851348L;
051
052        private static final Logger logger = LoggerFactory.getLogger(StructureImpl.class);
053
054        private String pdb_id ;
055
056        /* models is an ArrayList of ArrayLists */
057        private List<List<Chain>> models;
058
059        private List<Map <String,Integer>> connections ;
060        private List<Compound> compounds;
061        private List<DBRef> dbrefs;
062        private List<Bond> ssbonds;
063        private List<Site> sites;
064        private List<Group> hetAtoms;
065        private String name ;
066        private StructureIdentifier structureIdentifier;
067
068        private PDBHeader pdbHeader;
069
070        private Long id;
071        private boolean biologicalAssembly;
072
073        /**
074         *  Constructs a StructureImpl object.
075         */
076        public StructureImpl() {
077                super();
078
079                models         = new ArrayList<List<Chain>>();
080                name           = "";
081                connections    = new ArrayList<Map<String,Integer>>();
082                compounds      = new ArrayList<Compound>();
083                dbrefs         = new ArrayList<DBRef>();
084                pdbHeader      = new PDBHeader();
085                ssbonds        = new ArrayList<Bond>();
086                sites          = new ArrayList<Site>();
087                hetAtoms       = new ArrayList<Group>();
088        }
089
090        /** get the ID used by Hibernate
091         *
092         * @return the ID used by Hibernate
093         */
094        @Override
095        public Long getId() {
096                return id;
097        }
098
099        /** set the ID used by Hibernate
100         *
101         * @param id
102         */
103        @Override
104        public void setId(Long id) {
105                this.id = id;
106        }
107
108
109        /** construct a Structure object that only contains a single group
110         *
111         * @param g
112         */
113        public StructureImpl(Group g){
114                this();
115
116                Chain c = new ChainImpl();
117                c.addGroup(g);
118
119                addChain(c);
120        }
121
122        /** construct a Structure object that contains a particular chain
123         *
124         * @param c
125         */
126        public StructureImpl(Chain c){
127                this();
128                addChain(c);
129        }
130
131        /** returns an identical copy of this structure .
132         * @return an identical Structure object
133         */
134        @Override
135        public Structure clone() {
136                // Note: structures are also cloned in SubstructureIdentifier.reduce().
137                // Changes might need to be made there as well
138
139                Structure n = new StructureImpl();
140                // go through whole substructure and clone ...
141
142                // copy structure data
143
144                n.setPDBCode(getPDBCode());
145                n.setName(getName());
146                //TODO the header data is not being deep-copied, that's a minor issue since it is just some static metadata, but we should recheck this if needed - JD 2014-12-11
147                n.setPDBHeader(pdbHeader);
148                n.setDBRefs(this.getDBRefs());
149                n.setSites(getSites());
150
151
152                // go through each chain and clone chain
153                for (int i=0;i<nrModels();i++){
154                        List<Chain> cloned_model = new ArrayList<Chain>();
155
156                        for (int j=0;j<size(i);j++){
157
158                                Chain cloned_chain  = (Chain) getChain(i,j).clone();
159
160                                // setting the parent: can only be done from the parent
161                                cloned_chain.setStructure(n);
162
163                                cloned_model.add(cloned_chain);
164
165                        }
166                        n.addModel(cloned_model);
167
168                }
169
170                // deep-copying of Compounds is tricky: there's cross references also in the Chains
171                // beware: if we copy the compounds we would also need to reset the references to compounds in the individual chains
172                List<Compound> newCompoundList = new ArrayList<Compound>();
173                for (Compound compound:this.compounds) {
174                        Compound newCompound = new Compound(compound); // this sets everything but the chains
175                        for (String chainId:compound.getChainIds()) {
176
177                                        for (int modelNr=0;modelNr<n.nrModels();modelNr++) {
178                                                try {
179                                                        Chain newChain = n.getChainByPDB(chainId,modelNr);
180                                                        newChain.setCompound(newCompound);
181                                                        newCompound.addChain(newChain);
182                                                } catch (StructureException e) {
183                                                        // this actually happens for structure 1msh, which has no chain B for model 29 (clearly a deposition error)
184                                                        logger.warn("Could not find chain id "+chainId+" of model "+modelNr+" while cloning compound "+compound.getMolId()+". Something is wrong!");
185                                                }
186                                        }
187                        }
188                        newCompoundList.add(newCompound);
189                }
190                n.setCompounds(newCompoundList);
191
192                // TODO ssbonds are complicated to clone: there are deep references inside Atom objects, how would we do it? - JD 2016-03-03
193
194                return n ;
195        }
196
197
198        /** {@inheritDoc} */
199        @Override
200        public Group findGroup(String chainId, String pdbResnum, int modelnr)
201                        throws StructureException {
202
203
204                // if structure is xray there will be only one "model".
205                if ( modelnr > models.size())
206                        throw new StructureException(" no model nr " + modelnr +
207                                        " in this structure. (contains "+models.size()+")");
208
209
210                Chain c = findChain(chainId,modelnr);
211
212                List<Group> groups = c.getAtomGroups();
213
214                // now iterate over all groups in this chain.
215                // in order to find the amino acid that has this pdbRenum.
216
217                for (Group g : groups) {
218                        String rnum = g.getResidueNumber().toString();
219                        //System.out.println(g + " >" + rnum + "< >" + pdbResnum + "<");
220                        // we only mutate amino acids
221                        // and ignore hetatoms and nucleotides in this case
222                        if (rnum.equals(pdbResnum)) {
223                                return g;
224                        }
225                }
226
227                throw new StructureException("could not find group " + pdbResnum +
228                                " in chain " + chainId);
229        }
230
231
232        /** {@inheritDoc} */
233        @Override
234        public Group findGroup(String chainName, String pdbResnum) throws StructureException
235        {
236                return findGroup(chainName, pdbResnum, 0);
237
238        }
239
240
241
242
243        /** {@inheritDoc} */
244        @Override
245        public Chain findChain(String chainId, int modelnr) throws StructureException {
246
247                List<Chain> chains = getChains(modelnr);
248
249                // iterate over all chains.
250                for (Chain c : chains) {
251                        if (c.getChainID().equals(chainId)) {
252                                return c;
253                        }
254                }
255                throw new StructureException("Could not find chain \"" + chainId + "\" for PDB id " + pdb_id);
256        }
257
258
259        /** {@inheritDoc} */
260        @Override
261        public Chain findChain(String chainId) throws StructureException {
262
263                return findChain(chainId,0);
264        }
265
266
267        /** {@inheritDoc} */
268        @Override
269        public void setPDBCode (String pdb_id_) {
270                pdb_id = pdb_id_ ;
271        }
272
273        /** {@inheritDoc} */
274        @Override
275        public String  getPDBCode () {
276                return pdb_id ;
277        }
278
279
280
281        /** {@inheritDoc} */
282        @Override
283        public void   setName(String nam) { name = nam; }
284
285        /** {@inheritDoc} */
286        @Override
287        public String getName()           { return name;  }
288
289
290
291        /**
292         * @return The StructureIdentifier used to create this structure
293         */
294        @Override
295        public StructureIdentifier getStructureIdentifier() {
296                return structureIdentifier;
297        }
298
299        /**
300         * @param structureIdentifier the structureIdentifier corresponding to this structure
301         */
302        @Override
303        public void setStructureIdentifier(StructureIdentifier structureIdentifier) {
304                this.structureIdentifier = structureIdentifier;
305        }
306
307        /**
308         * {@inheritDoc}
309         */
310        @Override
311        public void      setConnections(List<Map<String,Integer>> conns) { connections = conns ; }
312
313        /**
314         * {@inheritDoc}
315         */
316        @Override
317        public List<Map<String,Integer>> getConnections()                { return connections ;}
318
319        /** {@inheritDoc} */
320        @Override
321        public void addChain(Chain chain) {
322                int modelnr = 0 ;
323                addChain(chain,modelnr);
324        }
325
326        /** {@inheritDoc} */
327        @Override
328        public void addChain(Chain chain, int modelnr) {
329                // if model has not been initialized, init it!
330                chain.setStructure(this);
331                if (models.isEmpty()) {
332                        List<Chain> model = new ArrayList<Chain>() ;
333                        model.add(chain);
334                        models.add(model);
335
336                } else {
337                        List<Chain> model = models.get(modelnr);
338                        model.add(chain);
339                }
340
341
342
343        }
344
345
346
347        /** {@inheritDoc} */
348        @Override
349        public Chain getChain(int number) {
350
351                int modelnr = 0 ;
352
353                return getChain(modelnr,number);
354        }
355
356
357        /** {@inheritDoc} */
358        @Override
359        public Chain getChain(int modelnr,int number) {
360
361                List<Chain> model  =  models.get(modelnr);
362
363                return model.get (number );
364        }
365
366
367
368        /** {@inheritDoc} */
369        @Override
370        public void addModel(List<Chain> model){
371                for (Chain c: model){
372                        c.setStructure(this);
373                }
374                models.add(model);
375        }
376
377
378        /** {@inheritDoc} */
379        @Override
380        public void setChains(List<Chain> chains){
381
382                setModel(0,chains);
383        }
384
385
386
387        /** {@inheritDoc} */
388        @Override
389        public void setModel(int position, List<Chain> model){
390                if (model == null)
391                        throw new IllegalArgumentException("trying to set model to null!");
392
393                for (Chain c: model)
394                        c.setStructure(this);
395
396                //System.out.println("model size:" + models.size());
397
398                if (models.isEmpty()){
399                        models.add(model);
400                } else {
401                        models.set(position, model);
402                }
403        }
404
405        /** string representation.
406         *
407         */
408        @Override
409        public String toString(){
410                String newline = System.getProperty("line.separator");
411                StringBuilder str = new StringBuilder();
412                str.append("structure ");
413                str.append(name);
414                str.append(" ");
415                str.append(pdb_id);
416                str.append(" ");
417
418                if ( nrModels()>1 ){
419                        str.append( " models: ");
420                        str.append(nrModels());
421                        str.append(newline) ;
422                }
423
424                str.append(pdbHeader);
425                str.append(newline) ;
426
427                for (int i=0;i<nrModels();i++){
428                        if ( nrModels()>1 ) {
429                                str.append(" model[");
430                                str.append(i);
431                                str.append("]:");
432                                str.append(newline);
433                        }
434                        str.append(" chains:");
435                        str.append(newline);
436
437                        for (int j=0;j<size(i);j++){
438
439                                Chain cha = getChain(i,j);
440                                List<Group> agr = cha.getAtomGroups(GroupType.AMINOACID);
441                                List<Group> hgr = cha.getAtomGroups(GroupType.HETATM);
442                                List<Group> ngr = cha.getAtomGroups(GroupType.NUCLEOTIDE);
443
444                                str.append("chain ").append(j).append(": >").append(cha.getChainID()).append("< ");
445                                if ( cha.getCompound() != null){
446                                        Compound comp = cha.getCompound();
447                                        String molName = comp.getMolName();
448                                        if ( molName != null){
449                                                str.append(molName);
450                                        }
451                                }
452
453
454                                str.append(newline);
455                                str.append(" length SEQRES: ").append(cha.getSeqResLength());
456                                str.append(" length ATOM: ").append(cha.getAtomLength());
457                                str.append(" aminos: ").append(agr.size());
458                                str.append(" hetatms: ").append(hgr.size());
459                                str.append(" nucleotides: ").append(ngr.size()).append(newline);
460                        }
461
462                }
463                str.append("DBRefs: ").append(dbrefs.size()).append(newline);
464                for (DBRef dbref: dbrefs){
465                        str.append(dbref.toPDB()).append(newline);
466                }
467                str.append("Molecules: ").append(newline);
468                for (Compound mol : compounds) {
469                        str.append(mol).append(newline);
470                }
471
472
473                return str.toString() ;
474        }
475
476        /** return number of chains , if NMR return number of chains of first model .
477         *
478         */
479        @Override
480        public int size() {
481                int modelnr = 0 ;
482
483                if (!models.isEmpty()) {
484                        return models.get(modelnr).size();
485                }
486                else {
487                        return 0 ;
488                }
489
490        }
491
492        /** return number of chains  of model.
493         *
494         */
495        @Override
496        public int size(int modelnr) { return getChains(modelnr).size();   }
497
498        // some NMR stuff :
499
500        /** return number of models. */
501        @Override
502        public int nrModels() {
503                return models.size() ;
504        }
505
506        /**
507         * Whether this Structure is a crystallographic structure or not.
508         * It will first check the experimental technique and if not present it will try
509         * to guess from the presence of a space group and sensible cell parameters
510         *
511         * @return true if crystallographic, false otherwise
512         */
513        @Override
514        public boolean isCrystallographic() {
515                if (pdbHeader.getExperimentalTechniques()!=null) {
516                        return ExperimentalTechnique.isCrystallographic(pdbHeader.getExperimentalTechniques());
517                } else {
518                        // no experimental technique known, we try to guess...
519                        if (pdbHeader.getCrystallographicInfo().getSpaceGroup()!=null) {
520                                if (pdbHeader.getCrystallographicInfo().getCrystalCell()==null) {
521                                        return false; // space group defined but no crystal cell: incomplete info, return false
522                                } else {
523                                        return pdbHeader.getCrystallographicInfo().getCrystalCell().isCellReasonable();
524                                }
525                        }
526                }
527                return false;
528        }
529
530        /**
531         * Whether this Structure is a NMR structure or not.
532         * It will first check the experimental technique and if not present it will try
533         * to guess from the presence of more than 1 model and from b-factors being 0 in first chain of first model
534         * @return true if NMR, false otherwise
535         */
536        @Override
537        public boolean isNmr() {
538
539                // old implementation was:
540                //return nmrflag;
541
542                if (pdbHeader.getExperimentalTechniques()!=null) {
543                        return ExperimentalTechnique.isNmr(pdbHeader.getExperimentalTechniques());
544                } else {
545                        // no experimental technique known, we try to guess...
546                        if (nrModels()>1) {
547                                if (pdbHeader.getCrystallographicInfo().getSpaceGroup()!=null) {
548                                        // multimodel, sg defined, but missing cell: must be NMR
549                                        if (pdbHeader.getCrystallographicInfo().getCrystalCell()==null)
550                                                return true;
551                                        // multi-model, sg defined and cell unreasonable: must be NMR
552                                        if (!pdbHeader.getCrystallographicInfo().getCrystalCell().isCellReasonable())
553                                                return true;
554                                } else {
555                                        // multi-model and missing space group: must be NMR
556                                        return true;
557                                }
558                        }
559                }
560                return false;
561        }
562
563        /** {@inheritDoc} */
564        @Override
565        @Deprecated
566        public void setNmr(boolean nmr) {
567                // old implementation was:
568                // this.nmrflag = nmr;
569        }
570
571
572        /** retrieve all chains of a model.
573         *
574         * @param modelnr  an int
575         * @return a List object
576         */
577        @Override
578        public List<Chain> getChains(int modelnr){
579                return getModel(modelnr);
580        }
581
582        /** {@inheritDoc} */
583        @Override
584        public List<Chain> getChains(){
585                return getModel(0);
586        }
587
588        /** {@inheritDoc} */
589        @Override
590        public void setChains(int modelnr, List<Chain> chains){
591                for (Chain c: chains){
592                        c.setStructure(this);
593                }
594                models.remove(modelnr);
595                models.add(modelnr, chains);
596
597        }
598
599        /** retrieve all Chains belonging to a model .
600         *
601         * @param modelnr  an int
602         * @return a List object
603         */
604        @Override
605        public List<Chain> getModel(int modelnr) {
606
607                return models.get(modelnr);
608        }
609
610
611
612
613        /** {@inheritDoc} */
614        @Override
615        public Chain getChainByPDB(String chainId, int modelnr)
616                        throws StructureException{
617
618                List<Chain> chains = getChains(modelnr);
619                for (Chain c : chains) {
620                        if (c.getChainID().equals(chainId)) {
621                                return c;
622                        }
623                }
624                throw new StructureException("did not find chain with chainId \"" + chainId + "\"" + " for PDB id " + pdb_id);
625
626        }
627
628
629        /** {@inheritDoc} */
630        @Override
631        public Chain getChainByPDB(String chainId)
632                        throws StructureException{
633                return getChainByPDB(chainId,0);
634        }
635
636
637        /** {@inheritDoc} */
638        @Override
639        public String toPDB() {
640                FileConvert f = new FileConvert(this) ;
641                return f.toPDB();
642        }
643
644        /** {@inheritDoc} */
645        @Override
646        public String toMMCIF() {
647                FileConvert f = new FileConvert(this);
648                return f.toMMCIF();
649        }
650
651        /** {@inheritDoc} */
652        @Override
653        public boolean hasChain(String chainId) {
654                int modelnr = 0;
655
656                List<Chain> chains = getChains(modelnr);
657                for (Chain c : chains) {
658                        // we check here with equals because we might want to distinguish between upper and lower case chains!
659                        if (c.getChainID().equals(chainId)) {
660                                return true;
661                        }
662                }
663                return false;
664        }
665
666        /** {@inheritDoc} */
667        @Override
668        public void setCompounds(List<Compound> molList){
669                this.compounds = molList;
670        }
671
672        /** {@inheritDoc} */
673        @Override
674        public void addCompound(Compound compound) {
675                this.compounds.add(compound);
676        }
677
678        /** {@inheritDoc} */
679        @Override
680        public List<Compound> getCompounds() {
681                // compounds are parsed from the PDB/mmCIF file normally
682                // but if the file is incomplete, it won't have the Compounds information and we try
683                // to guess it from the existing seqres/atom sequences
684                if (compounds==null || compounds.isEmpty()) {
685                        CompoundFinder cf = new CompoundFinder(this);
686                        this.compounds = cf.findCompounds();
687
688                        // now we need to set references in chains:
689                        for (Compound compound:compounds) {
690                                for (Chain c:compound.getChains()) {
691                                        c.setCompound(compound);
692                                }
693                        }
694                }
695                return compounds;
696        }
697
698        /** {@inheritDoc} */
699        @Override
700        public Compound getCompoundById(int molId) {
701                for (Compound mol : this.compounds){
702                        if (mol.getMolId()==molId){
703                                return mol;
704                        }
705                }
706                return null;
707        }
708
709
710        /** {@inheritDoc} */
711        @Override
712        public List<DBRef> getDBRefs() {
713                return dbrefs;
714        }
715
716
717        /** {@inheritDoc} */
718        @Override
719        public void setDBRefs(List<DBRef> dbrefs) {
720                if ( dbrefs == null)
721                        throw new IllegalArgumentException("trying to set dbrefs to null!");
722
723                for( DBRef ref : dbrefs){
724                        ref.setParent(this);
725                }
726                this.dbrefs = dbrefs;
727        }
728
729
730        /** {@inheritDoc} */
731        @Override
732        public PDBHeader getPDBHeader() {
733                return pdbHeader;
734        }
735
736        /** {@inheritDoc} */
737        @Override
738        public void setPDBHeader(PDBHeader pdbHeader){
739                this.pdbHeader = pdbHeader;
740        }
741
742        /** {@inheritDoc} */
743        @Override
744        public List<Bond> getSSBonds(){
745                return ssbonds;
746
747        }
748
749        /** {@inheritDoc} */
750        @Override
751        public void setSSBonds(List<Bond> ssbonds){
752                this.ssbonds = ssbonds;
753        }
754
755        /**
756         * Adds a single disulfide Bond to this structure
757         *
758         * @param ssbond the SSBond.
759         */
760        @Override
761        public void addSSBond(Bond ssbond){
762                ssbonds.add(ssbond);
763        }
764
765        /**
766         * Return whether or not the entry has an associated journal article
767         * or publication. The JRNL section is not mandatory and thus may not be
768         * present.
769         * @return flag if a JournalArticle could be found.
770         */
771        @Override
772        public boolean hasJournalArticle() {
773                return this.pdbHeader.hasJournalArticle();
774        }
775
776        /**
777         * get the associated publication as defined by the JRNL records in a PDB
778         * file.
779         * @return a JournalArticle
780         */
781        @Override
782        public JournalArticle getJournalArticle() {
783                return this.pdbHeader.getJournalArticle();
784        }
785
786        /**
787         * set the associated publication as defined by the JRNL records in a PDB
788         * file.
789         * @param journalArticle the article
790         */
791        @Override
792        public void setJournalArticle(JournalArticle journalArticle) {
793                this.pdbHeader.setJournalArticle(journalArticle);
794        }
795
796        /**
797         * @return the sites contained in this structure
798         */
799
800        @Override
801        public List<Site> getSites() {
802                return sites;
803        }
804
805        /**
806         * @param sites the sites to set in the structure
807         */
808
809        @Override
810        public void setSites(List<Site> sites) {
811                this.sites = sites;
812        }
813
814        /** Caution: we should probably remove this to avoid confusion. Currently this is always an empty list!
815         *
816         * @return a list of Groups listed in the HET records - this will not
817         * include any waters.
818         */
819
820        @Override
821        public List<Group> getHetGroups() {
822                return hetAtoms;
823        }
824
825        /**
826         * Sets a flag to indicate if this structure is a biological assembly
827         * @param biologicalAssembly true if biological assembly, otherwise false
828         * @since 3.2
829         */
830        @Override
831        public void setBiologicalAssembly(boolean biologicalAssembly) {
832                this.biologicalAssembly = biologicalAssembly;
833        }
834
835        /**
836         * Gets flag that indicates if this structure is a biological assembly
837         * @return the sites contained in this structure
838         * @since 3.2
839         */
840        @Override
841        public boolean isBiologicalAssembly() {
842                return biologicalAssembly;
843        }
844
845        /**
846         * Sets crystallographic information for this structure
847         * @param crystallographicInfo crystallographic information
848         * @since 3.2
849         */
850
851        @Override
852        public void setCrystallographicInfo(PDBCrystallographicInfo crystallographicInfo) {
853                this.pdbHeader.setCrystallographicInfo(crystallographicInfo);
854        }
855
856        /**
857         * Gets crystallographic information for this structure
858         * @return PDBCrystallographicInfo crystallographic information
859         * @since 3.2
860         */
861        @Override
862        public PDBCrystallographicInfo getCrystallographicInfo() {
863                return pdbHeader.getCrystallographicInfo();
864        }
865
866        /** {@inheritDoc} */
867        @Override
868        public String getIdentifier() {
869                //1. StructureIdentifier
870                if(getStructureIdentifier() != null) {
871                        return getStructureIdentifier().getIdentifier();
872                }
873                //2. Name
874                if(getName() != null) {
875                        return getName();
876                }
877                //3. PDBCode + ranges
878                return toCanonical().getIdentifier();
879        }
880
881        /** {@inheritDoc} */
882        @Deprecated
883        @Override
884        public String getPdbId() {
885                return pdb_id;
886        }
887
888        /** {@inheritDoc} */
889        @Override
890        public void resetModels() {
891                models = new ArrayList<List<Chain>>();
892        }
893        /** {@inheritDoc} */
894        @Deprecated
895        @Override
896        public List<ResidueRange> getResidueRanges() {
897                return toCanonical().getResidueRanges();
898        }
899        /** {@inheritDoc} */
900        @Deprecated
901        @Override
902        public List<String> getRanges() {
903                return ResidueRange.toStrings(getResidueRanges());
904        }
905
906        /**
907         * Creates a SubstructureIdentifier based on the residues in this Structure.
908         *
909         * Only the first and last residues of each chain are considered, so chains
910         * with gaps
911         * @return A {@link SubstructureIdentifier} with residue ranges constructed from each chain
912         */
913        private SubstructureIdentifier toCanonical() {
914                StructureIdentifier real = getStructureIdentifier();
915                if(real != null) {
916                        try {
917                                return real.toCanonical();
918                        } catch (StructureException e) {
919                                // generate fake one if needed
920                        }
921                }
922
923                // No identifier set, so generate based on residues present in the structure
924                List<ResidueRange> range = new ArrayList<ResidueRange>();
925                for (Chain chain : getChains()) {
926                        List<Group> groups = chain.getAtomGroups();
927                        ListIterator<Group> groupsIt = groups.listIterator();
928                        if(!groupsIt.hasNext()) {
929                                continue; // no groups in chain
930                        }
931                        Group g = groupsIt.next();
932                        ResidueNumber first = g.getResidueNumber();
933
934                        //TODO Detect missing intermediate residues -sbliven, 2015-01-28
935                        //Already better than previous whole-chain representation
936
937                        // get last residue
938                        while(groupsIt.hasNext()) {
939                                g = groupsIt.next();
940                        }
941                        ResidueNumber last = g.getResidueNumber();
942
943                        range.add(new ResidueRange(chain.getChainID(),first,last));
944                }
945                return new SubstructureIdentifier(getPDBCode(),range);
946        }
947
948}