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