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