JAL-1478 Fixed ArrayIndexOutOfBoundsException while performing NW mapping
[jalview.git] / src / MCview / PDBChain.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 MCview;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.datamodel.AlignmentAnnotation;
25 import jalview.datamodel.Annotation;
26 import jalview.datamodel.Mapping;
27 import jalview.datamodel.Sequence;
28 import jalview.datamodel.SequenceFeature;
29 import jalview.datamodel.SequenceI;
30 import jalview.schemes.ColourSchemeI;
31 import jalview.schemes.ResidueProperties;
32 import jalview.structure.StructureMapping;
33
34 import java.awt.Color;
35 import java.util.List;
36 import java.util.Vector;
37
38 public class PDBChain
39 {
40   /**
41    * SequenceFeature group for PDB File features added to sequences
42    */
43   private static final String PDBFILEFEATURE = "PDBFile";
44
45   private static final String IEASTATUS = "IEA:jalview";
46
47   public String id;
48
49   public Vector<Bond> bonds = new Vector<Bond>();
50
51   public Vector<Atom> atoms = new Vector<Atom>();
52
53   public Vector<Residue> residues = new Vector<Residue>();
54
55   public int offset;
56
57   /**
58    * sequence is the sequence extracted by the chain parsing code
59    */
60   public SequenceI sequence;
61
62   /**
63    * shadow is the sequence created by any other parsing processes (e.g. Jmol,
64    * RNAview)
65    */
66   public SequenceI shadow = null;
67
68   public boolean isNa = false;
69
70   public boolean isVisible = true;
71
72   public int pdbstart = 0;
73
74   public int pdbend = 0;
75
76   public int seqstart = 0;
77
78   public int seqend = 0;
79
80   public String pdbid = "";
81
82   public PDBChain(String pdbid, String id)
83   {
84     this.pdbid = pdbid.toLowerCase();
85     this.id = id;
86   }
87
88   /**
89    * character used to write newlines
90    */
91   protected String newline = System.getProperty("line.separator");
92
93   public Mapping shadowMap;
94
95   public void setNewlineString(String nl)
96   {
97     newline = nl;
98   }
99
100   public String getNewlineString()
101   {
102     return newline;
103   }
104
105   public String print()
106   {
107     StringBuilder tmp = new StringBuilder(256);
108
109     for (Bond b : bonds)
110     {
111       tmp.append(b.at1.resName).append(" ").append(b.at1.resNumber)
112               .append(" ").append(offset).append(newline);
113     }
114
115     return tmp.toString();
116   }
117
118   /**
119    * Annotate the residues with their corresponding positions in s1 using the
120    * alignment in as NOTE: This clears all atom.alignmentMapping values on the
121    * structure.
122    * 
123    * @param as
124    * @param s1
125    */
126   public void makeExactMapping(AlignSeq as, SequenceI s1)
127   {
128     int pdbpos = as.getSeq2Start() - 2;
129     int alignpos = s1.getStart() + as.getSeq1Start() - 3;
130     // first clear out any old alignmentMapping values:
131     for (Atom atom : atoms)
132     {
133       atom.alignmentMapping = -1;
134     }
135     // and now trace the alignment onto the atom set.
136     for (int i = 0; i < as.astr1.length(); i++)
137     {
138       if (as.astr1.charAt(i) != '-')
139       {
140         alignpos++;
141       }
142
143       if (as.astr2.charAt(i) != '-')
144       {
145         pdbpos++;
146       }
147
148       if (as.astr1.charAt(i) == as.astr2.charAt(i))
149       {
150         if (pdbpos >= residues.size())
151         {
152           continue;
153         }
154         Residue res = residues.elementAt(pdbpos);
155         for (Atom atom : res.atoms)
156         {
157           atom.alignmentMapping = alignpos;
158         }
159       }
160     }
161   }
162
163   /**
164    * copy over the RESNUM seqfeatures from the internal chain sequence to the
165    * mapped sequence
166    * 
167    * @param seq
168    * @param status
169    *          The Status of the transferred annotation
170    * @return the features added to sq (or its dataset)
171    */
172   public SequenceFeature[] transferRESNUMFeatures(SequenceI seq,
173           String status)
174   {
175     SequenceI sq = seq;
176     while (sq != null && sq.getDatasetSequence() != null)
177     {
178       sq = sq.getDatasetSequence();
179       if (sq == sequence)
180       {
181         return null;
182       }
183     }
184     /**
185      * Remove any existing features for this chain if they exist ?
186      * SequenceFeature[] seqsfeatures=seq.getSequenceFeatures(); int
187      * totfeat=seqsfeatures.length; // Remove any features for this exact chain
188      * ? for (int i=0; i<seqsfeatures.length; i++) { }
189      */
190     if (status == null)
191     {
192       status = PDBChain.IEASTATUS;
193     }
194     SequenceFeature[] features = sequence.getSequenceFeatures();
195     for (int i = 0; i < features.length; i++)
196     {
197       if (features[i].getFeatureGroup().equals(pdbid))
198       {
199         SequenceFeature tx = new SequenceFeature(features[i]);
200         tx.setBegin(1 + residues.elementAt(tx.getBegin() - offset).atoms
201                 .elementAt(0).alignmentMapping);
202         tx.setEnd(1 + residues.elementAt(tx.getEnd() - offset).atoms
203                 .elementAt(0).alignmentMapping);
204         tx.setStatus(status
205                 + ((tx.getStatus() == null || tx.getStatus().length() == 0) ? ""
206                         : ":" + tx.getStatus()));
207         if (tx.begin != 0 && tx.end != 0)
208         {
209           sq.addSequenceFeature(tx);
210         }
211       }
212     }
213     return features;
214   }
215
216   /**
217    * Traverses the list of residues and constructs bonds where CA-to-CA atoms or
218    * P-to-P atoms are found. Also sets the 'isNa' flag if more than 99% of
219    * residues contain a P not a CA.
220    */
221   public void makeCaBondList()
222   {
223     boolean na = false;
224     int numNa = 0;
225     for (int i = 0; i < (residues.size() - 1); i++)
226     {
227       Residue tmpres = residues.elementAt(i);
228       Residue tmpres2 = residues.elementAt(i + 1);
229       Atom at1 = tmpres.findAtom("CA");
230       Atom at2 = tmpres2.findAtom("CA");
231       na = false;
232       if ((at1 == null) && (at2 == null))
233       {
234         na = true;
235         at1 = tmpres.findAtom("P");
236         at2 = tmpres2.findAtom("P");
237       }
238       if ((at1 != null) && (at2 != null))
239       {
240         if (at1.chain.equals(at2.chain))
241         {
242           if (na)
243           {
244             numNa++;
245           }
246           makeBond(at1, at2);
247         }
248       }
249       else
250       {
251         System.out.println("not found " + i);
252       }
253     }
254
255     /*
256      * If > 99% 'P', flag as nucleotide; note the count doesn't include the last
257      * residue
258      */
259     if (residues.size() > 1 && (numNa / (residues.size() - 1) > 0.99))
260     {
261       isNa = true;
262     }
263   }
264
265   /**
266    * Construct a bond from atom1 to atom2 and add it to the list of bonds for
267    * this chain
268    * 
269    * @param at1
270    * @param at2
271    */
272   public void makeBond(Atom at1, Atom at2)
273   {
274     bonds.addElement(new Bond(at1, at2));
275   }
276
277   /**
278    * Traverses the list of atoms and
279    * <ul>
280    * <li>constructs a list of Residues, each containing all the atoms that share
281    * the same residue number</li>
282    * <li>adds a RESNUM sequence feature for each position</li>
283    * <li>creates the sequence string</li>
284    * <li>determines if nucleotide</li>
285    * <li>saves the residue number of the first atom as 'offset'</li>
286    * <li>adds temp factor annotation if the flag is set to do so</li>
287    * </ul>
288    * 
289    * @param visibleChainAnnotation
290    */
291   public void makeResidueList(boolean visibleChainAnnotation)
292   {
293     int count = 0;
294     Object symbol;
295     boolean deoxyn = false;
296     boolean nucleotide = false;
297     StringBuilder seq = new StringBuilder(256);
298     Vector<SequenceFeature> resFeatures = new Vector<SequenceFeature>();
299     Vector<Annotation> resAnnotation = new Vector<Annotation>();
300     int i, iSize = atoms.size() - 1;
301     int resNumber = -1;
302     char insCode = ' ';
303     for (i = 0; i <= iSize; i++)
304     {
305       Atom tmp = atoms.elementAt(i);
306       resNumber = tmp.resNumber;
307       insCode = tmp.insCode;
308
309       int res = resNumber;
310       char ins = insCode;
311
312       if (i == 0)
313       {
314         offset = resNumber;
315       }
316
317       Vector<Atom> resAtoms = new Vector<Atom>();
318       // Add atoms to a vector while the residue number
319       // remains the same as the first atom's resNumber (res)
320       while ((resNumber == res) && (ins == insCode) && (i < atoms.size()))
321       {
322         resAtoms.add(atoms.elementAt(i));
323         i++;
324
325         if (i < atoms.size())
326         {
327           resNumber = atoms.elementAt(i).resNumber;
328           insCode = atoms.elementAt(i).insCode;
329         }
330         else
331         {
332           resNumber++;
333         }
334       }
335
336       // We need this to keep in step with the outer for i = loop
337       i--;
338
339       // Add inserted residues as features to the base residue
340       Atom currAtom = resAtoms.get(0);
341       if (currAtom.insCode != ' '
342               && residues.lastElement().atoms.get(0).resNumber == currAtom.resNumber)
343       {
344         SequenceFeature sf = new SequenceFeature("INSERTION",
345                 currAtom.resName + ":" + currAtom.resNumIns + " " + pdbid
346                         + id, "", offset + count - 1, offset + count - 1,
347                 "PDB_INS");
348         resFeatures.addElement(sf);
349         residues.lastElement().atoms.addAll(resAtoms);
350       }
351       else
352       {
353
354       // Make a new Residue object with the new atoms vector
355       residues.addElement(new Residue(resAtoms, resNumber - 1, count));
356
357       Residue tmpres = residues.lastElement();
358       Atom tmpat = tmpres.atoms.get(0);
359       // Make A new SequenceFeature for the current residue numbering
360         SequenceFeature sf = new SequenceFeature("RESNUM", tmpat.resName
361               + ":" + tmpat.resNumIns + " " + pdbid + id, "", offset
362               + count, offset + count, pdbid);
363       // MCview.PDBChain.PDBFILEFEATURE);
364       resFeatures.addElement(sf);
365       resAnnotation.addElement(new Annotation(tmpat.tfactor));
366       // Keep totting up the sequence
367       if ((symbol = ResidueProperties.getAA3Hash().get(tmpat.resName)) == null)
368       {
369         String nucname = tmpat.resName.trim();
370         // use the aaIndex rather than call 'toLower' - which would take a bit
371         // more time.
372         deoxyn = nucname.length() == 2
373                 && ResidueProperties.aaIndex[nucname.charAt(0)] == ResidueProperties.aaIndex['D'];
374         if (tmpat.name.equalsIgnoreCase("CA")
375                 || ResidueProperties.nucleotideIndex[nucname
376                         .charAt((deoxyn ? 1 : 0))] == -1)
377         {
378           seq.append("X");
379           // System.err.println("PDBReader:Null aa3Hash for " +
380           // tmpat.resName);
381         }
382         else
383         {
384           // nucleotide flag
385           nucleotide = true;
386           seq.append(nucname.charAt((deoxyn ? 1 : 0)));
387         }
388       }
389       else
390       {
391         if (nucleotide)
392         {
393           System.err
394                   .println("Warning: mixed nucleotide and amino acid chain.. its gonna do bad things to you!");
395         }
396         seq.append(ResidueProperties.aa[((Integer) symbol).intValue()]);
397       }
398         count++;
399       }
400     }
401
402     if (id.length() < 1)
403     {
404       id = " ";
405     }
406     isNa = nucleotide;
407     sequence = new Sequence(id, seq.toString(), offset, resNumber - 1); // Note:
408     // resNumber-offset
409     // ~=
410     // seq.size()
411     // Add normalised feature scores to RESNUM indicating start/end of sequence
412     // sf.setScore(offset+count);
413
414     // System.out.println("PDB Sequence is :\nSequence = " + seq);
415     // System.out.println("No of residues = " + residues.size());
416     for (i = 0, iSize = resFeatures.size(); i < iSize; i++)
417     {
418       sequence.addSequenceFeature(resFeatures.elementAt(i));
419       resFeatures.setElementAt(null, i);
420     }
421     if (visibleChainAnnotation)
422     {
423       Annotation[] annots = new Annotation[resAnnotation.size()];
424       float max = 0;
425       for (i = 0, iSize = annots.length; i < iSize; i++)
426       {
427         annots[i] = resAnnotation.elementAt(i);
428         if (annots[i].value > max)
429         {
430           max = annots[i].value;
431         }
432         resAnnotation.setElementAt(null, i);
433       }
434
435       AlignmentAnnotation tfactorann = new AlignmentAnnotation(
436               "Temperature Factor", "Temperature Factor for " + pdbid + id,
437               annots, 0, max, AlignmentAnnotation.LINE_GRAPH);
438       tfactorann.setSequenceRef(sequence);
439       sequence.addAlignmentAnnotation(tfactorann);
440     }
441   }
442
443   /**
444    * Colour start/end of bonds by charge
445    * <ul>
446    * <li>ASP and GLU red</li>
447    * <li>LYS and ARG blue</li>
448    * <li>CYS yellow</li>
449    * <li>others light gray</li>
450    * </ul>
451    */
452   public void setChargeColours()
453   {
454     for (Bond b : bonds)
455     {
456       if (b.at1 != null && b.at2 != null)
457       {
458         b.startCol = getChargeColour(b.at1.resName);
459         b.endCol = getChargeColour(b.at2.resName);
460       }
461       else
462       {
463         b.startCol = Color.gray;
464         b.endCol = Color.gray;
465       }
466     }
467   }
468
469   public static Color getChargeColour(String resName)
470   {
471     Color result = Color.lightGray;
472     if ("ASP".equals(resName) || "GLU".equals(resName))
473     {
474       result = Color.red;
475     }
476     else if ("LYS".equals(resName) || "ARG".equals(resName))
477     {
478       result = Color.blue;
479     }
480     else if ("CYS".equals(resName))
481     {
482       result = Color.yellow;
483     }
484     return result;
485   }
486
487   /**
488    * Sets the start/end colours of bonds to those of the start/end atoms
489    * according to the specified colour scheme. Note: currently only works for
490    * peptide residues.
491    * 
492    * @param cs
493    */
494   public void setChainColours(ColourSchemeI cs)
495   {
496     int index;
497     for (Bond b : bonds)
498     {
499       try
500       {
501         index = ResidueProperties.aa3Hash.get(b.at1.resName).intValue();
502         b.startCol = cs.findColour(ResidueProperties.aa[index].charAt(0));
503
504         index = ResidueProperties.aa3Hash.get(b.at2.resName).intValue();
505         b.endCol = cs.findColour(ResidueProperties.aa[index].charAt(0));
506
507       } catch (Exception e)
508       {
509         b.startCol = Color.gray;
510         b.endCol = Color.gray;
511       }
512     }
513   }
514
515   public void setChainColours(Color col)
516   {
517     for (Bond b : bonds)
518     {
519       b.startCol = col;
520       b.endCol = col;
521     }
522   }
523
524   /**
525    * copy any sequence annotation onto the sequence mapped using the provided
526    * StructureMapping
527    * 
528    * @param mapping
529    *          - positional mapping between destination sequence and pdb resnum
530    * @param sqmpping
531    *          - mapping between destination sequence and local chain
532    */
533   public void transferResidueAnnotation(StructureMapping mapping,
534           jalview.datamodel.Mapping sqmpping)
535   {
536     SequenceI sq = mapping.getSequence();
537     SequenceI dsq = sq;
538     if (sq != null)
539     {
540       while (dsq.getDatasetSequence() != null)
541       {
542         dsq = dsq.getDatasetSequence();
543       }
544       // any annotation will be transferred onto the dataset sequence
545
546       if (shadow != null && shadow.getAnnotation() != null)
547       {
548
549         for (AlignmentAnnotation ana : shadow.getAnnotation())
550         {
551           List<AlignmentAnnotation> transfer = sq.getAlignmentAnnotations(
552                   ana.getCalcId(), ana.label);
553           if (transfer == null || transfer.size() == 0)
554           {
555             ana = new AlignmentAnnotation(ana);
556             ana.liftOver(sequence, shadowMap);
557             ana.liftOver(dsq, sqmpping);
558             dsq.addAlignmentAnnotation(ana);
559           }
560           else
561           {
562             continue;
563           }
564         }
565       }
566       else
567       {
568         if (sequence != null && sequence.getAnnotation() != null)
569         {
570           for (AlignmentAnnotation ana : sequence.getAnnotation())
571           {
572             List<AlignmentAnnotation> transfer = sq
573                     .getAlignmentAnnotations(ana.getCalcId(), ana.label);
574             if (transfer == null || transfer.size() == 0)
575             {
576               ana = new AlignmentAnnotation(ana);
577               ana.liftOver(dsq, sqmpping);
578               // mapping.transfer(ana);
579             }
580             else
581             {
582               continue;
583             }
584           }
585         }
586       }
587       if (false)
588       {
589         // Useful for debugging mappings - adds annotation for mapped position
590         float min = -1, max = 0;
591         Annotation[] an = new Annotation[sq.getEnd() - sq.getStart() + 1];
592         for (int i = sq.getStart(), j = sq.getEnd(), k = 0; i <= j; i++, k++)
593         {
594           int prn = mapping.getPDBResNum(k + 1);
595
596           an[k] = new Annotation(prn);
597           if (min == -1)
598           {
599             min = k;
600             max = k;
601           }
602           else
603           {
604             if (min > k)
605             {
606               min = k;
607             }
608             else if (max < k)
609             {
610               max = k;
611             }
612           }
613         }
614         sq.addAlignmentAnnotation(new AlignmentAnnotation("PDB.RESNUM",
615                 "PDB Residue Numbering for " + this.pdbid + ":" + this.id,
616                 an, min, max, AlignmentAnnotation.LINE_GRAPH));
617       }
618     }
619   }
620 }