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