61597573f2b62fd5a88dc85181f0a821de0f0bde
[jalview.git] / src / jalview / structures / models / AAStructureBindingModel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.structures.models;
22
23 import jalview.api.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.api.SequenceRenderer;
26 import jalview.api.StructureSelectionManagerProvider;
27 import jalview.api.structures.JalviewStructureDisplayI;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.HiddenColumns;
30 import jalview.datamodel.PDBEntry;
31 import jalview.datamodel.SequenceI;
32 import jalview.io.DataSourceType;
33 import jalview.schemes.ColourSchemeI;
34 import jalview.structure.AtomSpec;
35 import jalview.structure.StructureListener;
36 import jalview.structure.StructureMapping;
37 import jalview.structure.StructureMappingcommandSet;
38 import jalview.structure.StructureSelectionManager;
39 import jalview.util.Comparison;
40 import jalview.util.MessageManager;
41
42 import java.awt.Color;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.BitSet;
46 import java.util.List;
47
48 /**
49  * 
50  * A base class to hold common function for protein structure model binding.
51  * Initial version created by refactoring JMol and Chimera binding models, but
52  * other structure viewers could in principle be accommodated in future.
53  * 
54  * @author gmcarstairs
55  *
56  */
57 public abstract class AAStructureBindingModel
58         extends SequenceStructureBindingModel
59         implements StructureListener, StructureSelectionManagerProvider
60 {
61
62   private StructureSelectionManager ssm;
63
64   /*
65    * distinct PDB entries (pdb files) associated
66    * with sequences
67    */
68   private PDBEntry[] pdbEntry;
69
70   /*
71    * sequences mapped to each pdbentry
72    */
73   private SequenceI[][] sequence;
74
75   /*
76    * array of target chains for sequences - tied to pdbentry and sequence[]
77    */
78   private String[][] chains;
79
80   /*
81    * datasource protocol for access to PDBEntrylatest
82    */
83   DataSourceType protocol = null;
84
85   protected boolean colourBySequence = true;
86
87   private boolean nucleotide;
88
89   private boolean finishedInit = false;
90
91   /**
92    * current set of model filenames loaded in the Jmol instance
93    */
94   protected String[] modelFileNames = null;
95
96   public String fileLoadingError;
97
98   private boolean showAlignmentOnly;
99
100   /**
101    * Data bean class to simplify parameterisation in superposeStructures
102    */
103   protected class SuperposeData
104   {
105     /**
106      * Constructor with alignment width argument
107      * 
108      * @param width
109      */
110     public SuperposeData(int width)
111     {
112       pdbResNo = new int[width];
113     }
114
115     public String filename;
116
117     public String pdbId;
118
119     public String chain = "";
120
121     public boolean isRna;
122
123     /*
124      * The pdb residue number (if any) mapped to each column of the alignment
125      */
126     public int[] pdbResNo;
127   }
128
129   /**
130    * Constructor
131    * 
132    * @param ssm
133    * @param seqs
134    */
135   public AAStructureBindingModel(StructureSelectionManager ssm,
136           SequenceI[][] seqs)
137   {
138     this.ssm = ssm;
139     this.sequence = seqs;
140   }
141
142   /**
143    * Constructor
144    * 
145    * @param ssm
146    * @param pdbentry
147    * @param sequenceIs
148    * @param protocol
149    */
150   public AAStructureBindingModel(StructureSelectionManager ssm,
151           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
152           DataSourceType protocol)
153   {
154     this.ssm = ssm;
155     this.sequence = sequenceIs;
156     this.nucleotide = Comparison.isNucleotide(sequenceIs);
157     this.pdbEntry = pdbentry;
158     this.protocol = protocol;
159     resolveChains();
160   }
161
162   private boolean resolveChains()
163   {
164     /**
165      * final count of chain mappings discovered
166      */
167     int chainmaps = 0;
168     // JBPNote: JAL-2693 - this should be a list of chain mappings per
169     // [pdbentry][sequence]
170     String[][] newchains = new String[pdbEntry.length][];
171     int pe = 0;
172     for (PDBEntry pdb : pdbEntry)
173     {
174       SequenceI[] seqsForPdb = sequence[pe];
175       if (seqsForPdb != null)
176       {
177         newchains[pe] = new String[seqsForPdb.length];
178         int se = 0;
179         for (SequenceI asq : seqsForPdb)
180         {
181           String chain = (chains != null && chains[pe] != null)
182                   ? chains[pe][se]
183                   : null;
184           SequenceI sq = (asq.getDatasetSequence() == null) ? asq
185                   : asq.getDatasetSequence();
186           if (sq.getAllPDBEntries() != null)
187           {
188             for (PDBEntry pdbentry : sq.getAllPDBEntries())
189             {
190               if (pdb.getFile() != null && pdbentry.getFile() != null
191                       && pdb.getFile().equals(pdbentry.getFile()))
192               {
193                 String chaincode = pdbentry.getChainCode();
194                 if (chaincode != null && chaincode.length() > 0)
195                 {
196                   chain = chaincode;
197                   chainmaps++;
198                   break;
199                 }
200               }
201             }
202           }
203           newchains[pe][se] = chain;
204           se++;
205         }
206         pe++;
207       }
208     }
209
210     chains = newchains;
211     return chainmaps > 0;
212   }
213   public StructureSelectionManager getSsm()
214   {
215     return ssm;
216   }
217
218   /**
219    * Returns the i'th PDBEntry (or null)
220    * 
221    * @param i
222    * @return
223    */
224   public PDBEntry getPdbEntry(int i)
225   {
226     return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
227   }
228
229   /**
230    * Answers true if this binding includes the given PDB id, else false
231    * 
232    * @param pdbId
233    * @return
234    */
235   public boolean hasPdbId(String pdbId)
236   {
237     if (pdbEntry != null)
238     {
239       for (PDBEntry pdb : pdbEntry)
240       {
241         if (pdb.getId().equals(pdbId))
242         {
243           return true;
244         }
245       }
246     }
247     return false;
248   }
249
250   /**
251    * Returns the number of modelled PDB file entries.
252    * 
253    * @return
254    */
255   public int getPdbCount()
256   {
257     return pdbEntry == null ? 0 : pdbEntry.length;
258   }
259
260   public SequenceI[][] getSequence()
261   {
262     return sequence;
263   }
264
265   public String[][] getChains()
266   {
267     return chains;
268   }
269
270   public DataSourceType getProtocol()
271   {
272     return protocol;
273   }
274
275   // TODO may remove this if calling methods can be pulled up here
276   protected void setPdbentry(PDBEntry[] pdbentry)
277   {
278     this.pdbEntry = pdbentry;
279   }
280
281   protected void setSequence(SequenceI[][] sequence)
282   {
283     this.sequence = sequence;
284   }
285
286   protected void setChains(String[][] chains)
287   {
288     this.chains = chains;
289   }
290
291   /**
292    * Construct a title string for the viewer window based on the data Jalview
293    * knows about
294    * 
295    * @param viewerName
296    *          TODO
297    * @param verbose
298    * 
299    * @return
300    */
301   public String getViewerTitle(String viewerName, boolean verbose)
302   {
303     if (getSequence() == null || getSequence().length < 1
304             || getPdbCount() < 1 || getSequence()[0].length < 1)
305     {
306       return ("Jalview " + viewerName + " Window");
307     }
308     // TODO: give a more informative title when multiple structures are
309     // displayed.
310     StringBuilder title = new StringBuilder(64);
311     final PDBEntry pdbe = getPdbEntry(0);
312     title.append(viewerName + " view for " + getSequence()[0][0].getName()
313             + ":" + pdbe.getId());
314
315     if (verbose)
316     {
317       String method = (String) pdbe.getProperty("method");
318       if (method != null)
319       {
320         title.append(" Method: ").append(method);
321       }
322       String chain = (String) pdbe.getProperty("chains");
323       if (chain != null)
324       {
325         title.append(" Chain:").append(chain);
326       }
327     }
328     return title.toString();
329   }
330
331   /**
332    * Called by after closeViewer is called, to release any resources and
333    * references so they can be garbage collected. Override if needed.
334    */
335   protected void releaseUIResources()
336   {
337
338   }
339
340   public boolean isColourBySequence()
341   {
342     return colourBySequence;
343   }
344
345   public void setColourBySequence(boolean colourBySequence)
346   {
347     this.colourBySequence = colourBySequence;
348   }
349
350   protected void addSequenceAndChain(int pe, SequenceI[] seq,
351           String[] tchain)
352   {
353     if (pe < 0 || pe >= getPdbCount())
354     {
355       throw new Error(MessageManager.formatMessage(
356               "error.implementation_error_no_pdbentry_from_index",
357               new Object[]
358               { Integer.valueOf(pe).toString() }));
359     }
360     final String nullChain = "TheNullChain";
361     List<SequenceI> s = new ArrayList<>();
362     List<String> c = new ArrayList<>();
363     if (getChains() == null)
364     {
365       setChains(new String[getPdbCount()][]);
366     }
367     if (getSequence()[pe] != null)
368     {
369       for (int i = 0; i < getSequence()[pe].length; i++)
370       {
371         s.add(getSequence()[pe][i]);
372         if (getChains()[pe] != null)
373         {
374           if (i < getChains()[pe].length)
375           {
376             c.add(getChains()[pe][i]);
377           }
378           else
379           {
380             c.add(nullChain);
381           }
382         }
383         else
384         {
385           if (tchain != null && tchain.length > 0)
386           {
387             c.add(nullChain);
388           }
389         }
390       }
391     }
392     for (int i = 0; i < seq.length; i++)
393     {
394       if (!s.contains(seq[i]))
395       {
396         s.add(seq[i]);
397         if (tchain != null && i < tchain.length)
398         {
399           c.add(tchain[i] == null ? nullChain : tchain[i]);
400         }
401       }
402     }
403     SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
404     getSequence()[pe] = tmp;
405     if (c.size() > 0)
406     {
407       String[] tch = c.toArray(new String[c.size()]);
408       for (int i = 0; i < tch.length; i++)
409       {
410         if (tch[i] == nullChain)
411         {
412           tch[i] = null;
413         }
414       }
415       getChains()[pe] = tch;
416     }
417     else
418     {
419       getChains()[pe] = null;
420     }
421   }
422
423   /**
424    * add structures and any known sequence associations
425    * 
426    * @returns the pdb entries added to the current set.
427    */
428   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
429           SequenceI[][] seq, String[][] chns)
430   {
431     List<PDBEntry> v = new ArrayList<>();
432     List<int[]> rtn = new ArrayList<>();
433     for (int i = 0; i < getPdbCount(); i++)
434     {
435       v.add(getPdbEntry(i));
436     }
437     for (int i = 0; i < pdbe.length; i++)
438     {
439       int r = v.indexOf(pdbe[i]);
440       if (r == -1 || r >= getPdbCount())
441       {
442         rtn.add(new int[] { v.size(), i });
443         v.add(pdbe[i]);
444       }
445       else
446       {
447         // just make sure the sequence/chain entries are all up to date
448         addSequenceAndChain(r, seq[i], chns[i]);
449       }
450     }
451     pdbe = v.toArray(new PDBEntry[v.size()]);
452     setPdbentry(pdbe);
453     if (rtn.size() > 0)
454     {
455       // expand the tied sequence[] and string[] arrays
456       SequenceI[][] sqs = new SequenceI[getPdbCount()][];
457       String[][] sch = new String[getPdbCount()][];
458       System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
459       System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
460       setSequence(sqs);
461       setChains(sch);
462       pdbe = new PDBEntry[rtn.size()];
463       for (int r = 0; r < pdbe.length; r++)
464       {
465         int[] stri = (rtn.get(r));
466         // record the pdb file as a new addition
467         pdbe[r] = getPdbEntry(stri[0]);
468         // and add the new sequence/chain entries
469         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
470       }
471     }
472     else
473     {
474       pdbe = null;
475     }
476     return pdbe;
477   }
478
479   /**
480    * Add sequences to the pe'th pdbentry's sequence set.
481    * 
482    * @param pe
483    * @param seq
484    */
485   public void addSequence(int pe, SequenceI[] seq)
486   {
487     addSequenceAndChain(pe, seq, null);
488   }
489
490   /**
491    * add the given sequences to the mapping scope for the given pdb file handle
492    * 
493    * @param pdbFile
494    *          - pdbFile identifier
495    * @param seq
496    *          - set of sequences it can be mapped to
497    */
498   public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
499   {
500     for (int pe = 0; pe < getPdbCount(); pe++)
501     {
502       if (getPdbEntry(pe).getFile().equals(pdbFile))
503       {
504         addSequence(pe, seq);
505       }
506     }
507   }
508
509   @Override
510   public abstract void highlightAtoms(List<AtomSpec> atoms);
511
512   protected boolean isNucleotide()
513   {
514     return this.nucleotide;
515   }
516
517   /**
518    * Returns a readable description of all mappings for the wrapped pdbfile to
519    * any mapped sequences
520    * 
521    * @param pdbfile
522    * @param seqs
523    * @return
524    */
525   public String printMappings()
526   {
527     if (pdbEntry == null)
528     {
529       return "";
530     }
531     StringBuilder sb = new StringBuilder(128);
532     for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
533     {
534       String pdbfile = getPdbEntry(pdbe).getFile();
535       List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
536       sb.append(getSsm().printMappings(pdbfile, seqs));
537     }
538     return sb.toString();
539   }
540
541   /**
542    * Returns the mapped structure position for a given aligned column of a given
543    * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
544    * not mapped to structure.
545    * 
546    * @param seq
547    * @param alignedPos
548    * @param mapping
549    * @return
550    */
551   protected int getMappedPosition(SequenceI seq, int alignedPos,
552           StructureMapping mapping)
553   {
554     if (alignedPos >= seq.getLength())
555     {
556       return -1;
557     }
558
559     if (Comparison.isGap(seq.getCharAt(alignedPos)))
560     {
561       return -1;
562     }
563     int seqPos = seq.findPosition(alignedPos);
564     int pos = mapping.getPDBResNum(seqPos);
565     return pos;
566   }
567
568   /**
569    * Helper method to identify residues that can participate in a structure
570    * superposition command. For each structure, identify a sequence in the
571    * alignment which is mapped to the structure. Identify non-gapped columns in
572    * the sequence which have a mapping to a residue in the structure. Returns
573    * the index of the first structure that has a mapping to the alignment.
574    * 
575    * @param alignment
576    *          the sequence alignment which is the basis of structure
577    *          superposition
578    * @param matched
579    *          a BitSet, where bit j is set to indicate that every structure has
580    *          a mapped residue present in column j (so the column can
581    *          participate in structure alignment)
582    * @param structures
583    *          an array of data beans corresponding to pdb file index
584    * @return
585    */
586   protected int findSuperposableResidues(AlignmentI alignment,
587           BitSet matched, SuperposeData[] structures)
588   {
589     int refStructure = -1;
590     String[] files = getStructureFiles();
591     if (files == null)
592     {
593       return -1;
594     }
595     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
596     {
597       StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
598       int lastPos = -1;
599
600       /*
601        * Find the first mapped sequence (if any) for this PDB entry which is in
602        * the alignment
603        */
604       final int seqCountForPdbFile = getSequence()[pdbfnum].length;
605       for (int s = 0; s < seqCountForPdbFile; s++)
606       {
607         for (StructureMapping mapping : mappings)
608         {
609           final SequenceI theSequence = getSequence()[pdbfnum][s];
610           if (mapping.getSequence() == theSequence
611                   && alignment.findIndex(theSequence) > -1)
612           {
613             if (refStructure < 0)
614             {
615               refStructure = pdbfnum;
616             }
617             for (int r = 0; r < alignment.getWidth(); r++)
618             {
619               if (!matched.get(r))
620               {
621                 continue;
622               }
623               int pos = getMappedPosition(theSequence, r, mapping);
624               if (pos < 1 || pos == lastPos)
625               {
626                 matched.clear(r);
627                 continue;
628               }
629               lastPos = pos;
630               structures[pdbfnum].pdbResNo[r] = pos;
631             }
632             String chain = mapping.getChain();
633             if (chain != null && chain.trim().length() > 0)
634             {
635               structures[pdbfnum].chain = chain;
636             }
637             structures[pdbfnum].pdbId = mapping.getPdbId();
638             structures[pdbfnum].isRna = theSequence.getRNA() != null;
639
640             /*
641              * move on to next pdb file (ignore sequences for other chains
642              * for the same structure)
643              */
644             s = seqCountForPdbFile;
645             break;
646           }
647         }
648       }
649     }
650     return refStructure;
651   }
652
653   /**
654    * Returns true if the structure viewer has loaded all of the files of
655    * interest (identified by the file mapping having been set up), or false if
656    * any are still not loaded after a timeout interval.
657    * 
658    * @param files
659    */
660   protected boolean waitForFileLoad(String[] files)
661   {
662     /*
663      * give up after 10 secs plus 1 sec per file
664      */
665     long starttime = System.currentTimeMillis();
666     long endTime = 10000 + 1000 * files.length + starttime;
667     String notLoaded = null;
668
669     boolean waiting = true;
670     while (waiting && System.currentTimeMillis() < endTime)
671     {
672       waiting = false;
673       for (String file : files)
674       {
675         notLoaded = file;
676         if (file == null)
677         {
678           continue;
679         }
680         try
681         {
682           StructureMapping[] sm = getSsm().getMapping(file);
683           if (sm == null || sm.length == 0)
684           {
685             waiting = true;
686           }
687         } catch (Throwable x)
688         {
689           waiting = true;
690         }
691       }
692     }
693
694     if (waiting)
695     {
696       System.err.println(
697               "Timed out waiting for structure viewer to load file "
698                       + notLoaded);
699       return false;
700     }
701     return true;
702   }
703
704   @Override
705   public boolean isListeningFor(SequenceI seq)
706   {
707     if (sequence != null)
708     {
709       for (SequenceI[] seqs : sequence)
710       {
711         if (seqs != null)
712         {
713           for (SequenceI s : seqs)
714           {
715             if (s == seq || (s.getDatasetSequence() != null
716                     && s.getDatasetSequence() == seq.getDatasetSequence()))
717             {
718               return true;
719             }
720           }
721         }
722       }
723     }
724     return false;
725   }
726
727   public boolean isFinishedInit()
728   {
729     return finishedInit;
730   }
731
732   public void setFinishedInit(boolean fi)
733   {
734     this.finishedInit = fi;
735   }
736
737   /**
738    * Returns a list of chains mapped in this viewer.
739    * 
740    * @return
741    */
742   public abstract List<String> getChainNames();
743
744   /**
745    * Returns the Jalview panel hosting the structure viewer (if any)
746    * 
747    * @return
748    */
749   public JalviewStructureDisplayI getViewer()
750   {
751     return null;
752   }
753
754   public abstract void setJalviewColourScheme(ColourSchemeI cs);
755
756   /**
757    * Constructs and sends a command to align structures against a reference
758    * structure, based on one or more sequence alignments. May optionally return
759    * an error or warning message for the alignment command.
760    * 
761    * @param alignments
762    *          an array of alignments to process
763    * @param structureIndices
764    *          an array of corresponding reference structures (index into pdb
765    *          file array); if a negative value is passed, the first PDB file
766    *          mapped to an alignment sequence is used as the reference for
767    *          superposition
768    * @param hiddenCols
769    *          an array of corresponding hidden columns for each alignment
770    * @return
771    */
772   public abstract String superposeStructures(AlignmentI[] alignments,
773           int[] structureIndices, HiddenColumns[] hiddenCols);
774
775   public abstract void setBackgroundColour(Color col);
776
777   protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
778           String[] files, SequenceRenderer sr, AlignmentViewPanel avp);
779
780   /**
781    * returns the current sequenceRenderer that should be used to colour the
782    * structures
783    * 
784    * @param alignment
785    * 
786    * @return
787    */
788   public abstract SequenceRenderer getSequenceRenderer(
789           AlignmentViewPanel alignment);
790
791   protected abstract void colourBySequence(
792           StructureMappingcommandSet[] colourBySequenceCommands);
793
794   public abstract void colourByChain();
795
796   public abstract void colourByCharge();
797
798   /**
799    * Recolours the displayed structures, if they are coloured by sequence, or
800    * 'show only visible alignment' is selected. This supports updating structure
801    * colours on either change of alignment colours, or change to the visible
802    * region of the alignment.
803    */
804   public void colourBySequence(AlignmentViewPanel alignmentv)
805   {
806     if (!isLoadingFinished())
807     {
808       return;
809     }
810     // todo: property change event for visibleAlignment
811     // to avoid unnecessary redraws here
812     if (!colourBySequence && !isShowAlignmentOnly())
813     {
814       return;
815     }
816     if (getSsm() == null)
817     {
818       return;
819     }
820     String[] files = getStructureFiles();
821
822     SequenceRenderer sr = getSequenceRenderer(alignmentv);
823
824     StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands(
825             files, sr, alignmentv);
826     colourBySequence(colourBySequenceCommands);
827   }
828
829   public boolean hasFileLoadingError()
830   {
831     return fileLoadingError != null && fileLoadingError.length() > 0;
832   }
833
834   public abstract jalview.api.FeatureRenderer getFeatureRenderer(
835           AlignmentViewPanel alignment);
836
837   /**
838    * Sets the flag for whether only mapped visible residues in the alignment
839    * should be visible in the structure viewer
840    * 
841    * @param b
842    */
843   public void setShowAlignmentOnly(boolean b)
844   {
845     showAlignmentOnly = b;
846   }
847
848   /**
849    * Answers true if only mapped visible residues in the alignment should be
850    * visible in the structure viewer, else false
851    * 
852    * @return
853    */
854   public boolean isShowAlignmentOnly()
855   {
856     return showAlignmentOnly;
857   }
858
859   /**
860    * Shows the structures in the viewer, without changing their colouring. This is
861    * to support toggling of whether the whole structure is shown, or only residues
862    * mapped to visible regions of the alignment.
863    * 
864    * @param alignViewportI
865    */
866   public void showStructures(AlignViewportI alignViewportI) 
867   {
868     // override with implementation
869   }
870
871   @Override
872   public void updateColours(Object source)
873   {
874     AlignmentViewPanel ap = (AlignmentViewPanel) source;
875     // ignore events from panels not used to colour this view
876     if (!getViewer().isUsedforcolourby(ap))
877     {
878       return;
879     }
880     if (!isLoadingFromArchive())
881     {
882       colourBySequence(ap);
883     }
884   }
885 }