JAL-2137 prevent NPE when inFile is null. Not sure this is correct ?
[jalview.git] / src / jalview / ext / jmol / JmolParser.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.ext.jmol;
22
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.Annotation;
25 import jalview.datamodel.DBRefSource;
26 import jalview.datamodel.SequenceI;
27 import jalview.io.FileParse;
28 import jalview.io.StructureFile;
29 import jalview.schemes.ResidueProperties;
30 import jalview.structure.StructureViewSettings;
31 import jalview.util.MessageManager;
32
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Vector;
38
39 import javajs.awt.Dimension;
40
41 import org.jmol.api.JmolStatusListener;
42 import org.jmol.api.JmolViewer;
43 import org.jmol.c.CBK;
44 import org.jmol.c.STR;
45 import org.jmol.modelset.ModelSet;
46 import org.jmol.viewer.Viewer;
47
48 import MCview.Atom;
49 import MCview.PDBChain;
50 import MCview.Residue;
51
52 /**
53  * Import and process files with Jmol for file like PDB, mmCIF
54  * 
55  * @author jprocter
56  * 
57  */
58 public class JmolParser extends StructureFile implements JmolStatusListener
59 {
60   Viewer viewer = null;
61
62   public JmolParser(boolean addAlignmentAnnotations, boolean predictSecStr,
63           boolean externalSecStr, String inFile, String type)
64           throws IOException
65   {
66     super(inFile, type);
67   }
68
69   public JmolParser(boolean addAlignmentAnnotations, boolean predictSecStr,
70           boolean externalSecStr, FileParse fp) throws IOException
71   {
72     super(fp);
73   }
74
75   public JmolParser()
76   {
77   }
78
79   /**
80    * Calls the Jmol library to parse the PDB/mmCIF file, and then inspects the
81    * resulting object model to generate Jalview-style sequences, with secondary
82    * structure annotation added where available (i.e. where it has been computed
83    * by Jmol using DSSP).
84    * 
85    * @see jalview.io.AlignFile#parse()
86    */
87   @Override
88   public void parse() throws IOException
89   {
90     String dataName = getDataName();
91     if (dataName.endsWith(".cif"))
92     {
93       setDbRefType(DBRefSource.MMCIF);
94     }
95     else
96     {
97       setDbRefType(DBRefSource.PDB);
98     }
99     setChains(new Vector<PDBChain>());
100     Viewer jmolModel = getJmolData();
101     jmolModel.openReader(getDataName(), getDataName(), getReader());
102     waitForScript(jmolModel);
103
104     /*
105      * Convert one or more Jmol Model objects to Jalview sequences
106      */
107     if (jmolModel.ms.mc > 0)
108     {
109       transformJmolModelToJalview(jmolModel.ms);
110     }
111   }
112
113   /**
114    * create a headless jmol instance for dataprocessing
115    * 
116    * @return
117    */
118   private Viewer getJmolData()
119   {
120     if (viewer == null)
121     {
122       try
123       {
124         viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null,
125                 null, "-x -o -n", this);
126         // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used
127         viewer.setBooleanProperty("defaultStructureDSSP", true);
128       } catch (ClassCastException x)
129       {
130         throw new Error(MessageManager.formatMessage(
131                 "error.jmol_version_not_compatible_with_jalview_version",
132                 new String[] { JmolViewer.getJmolVersion() }), x);
133       }
134     }
135     return viewer;
136   }
137
138   public void transformJmolModelToJalview(ModelSet ms) throws IOException
139   {
140     try
141     {
142       String lastID = "";
143       List<SequenceI> rna = new ArrayList<SequenceI>();
144       List<SequenceI> prot = new ArrayList<SequenceI>();
145       PDBChain tmpchain;
146       String pdbId = (String) ms.getInfo(0, "title");
147       setId(pdbId);
148       List<Atom> significantAtoms = convertSignificantAtoms(ms);
149       for (Atom tmpatom : significantAtoms)
150       {
151         try
152         {
153           tmpchain = findChain(tmpatom.chain);
154           if (tmpatom.resNumIns.trim().equals(lastID))
155           {
156             // phosphorylated protein - seen both CA and P..
157             continue;
158           }
159           tmpchain.atoms.addElement(tmpatom);
160         } catch (Exception e)
161         {
162           tmpchain = new PDBChain(pdbId, tmpatom.chain);
163           getChains().add(tmpchain);
164           tmpchain.atoms.addElement(tmpatom);
165         }
166         lastID = tmpatom.resNumIns.trim();
167       }
168       xferSettings();
169
170       makeResidueList();
171       makeCaBondList();
172
173       if (getId() == null)
174       {
175         // always use resource name, not the hardwired file
176         // Does the value of ID get used ? Behaviour needs to be
177         // documented and tested
178         setId(getDataName());
179       }
180       for (PDBChain chain : getChains())
181       {
182         SequenceI chainseq = postProcessChain(chain);
183         if (isRNA(chainseq))
184         {
185           rna.add(chainseq);
186         }
187         else
188         {
189           prot.add(chainseq);
190         }
191
192         if (StructureViewSettings.isPredictSecondaryStructure())
193         {
194           createAnnotation(chainseq, chain, ms.at);
195         }
196       }
197     } catch (OutOfMemoryError er)
198     {
199       System.out
200               .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
201       throw new IOException(
202               MessageManager
203                       .getString("exception.outofmemory_loading_mmcif_file"));
204     }
205   }
206
207   private List<Atom> convertSignificantAtoms(ModelSet ms)
208   {
209     List<Atom> significantAtoms = new ArrayList<Atom>();
210     for (org.jmol.modelset.Atom atom : ms.at)
211     {
212       // System.out.println("Seq Id : " + atom.getSeqID());
213       // System.out.println("To String : " + atom.toString());
214       if (atom.isHetero())
215       {
216         continue;
217       }
218       if (atom.getAtomName().equalsIgnoreCase("CA")
219               || atom.getAtomName().equalsIgnoreCase("P"))
220       {
221         Atom curAtom = new Atom(atom.x, atom.y, atom.z);
222         curAtom.atomIndex = atom.getIndex();
223         curAtom.chain = atom.getChainIDStr();
224         curAtom.insCode = atom.group.getInsertionCode();
225         curAtom.name = atom.getAtomName();
226         curAtom.number = atom.getAtomNumber();
227         curAtom.resName = atom.getGroup3(true);
228         curAtom.resNumber = atom.getResno();
229         curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom
230                 .getIndex()] : Float.valueOf(atom.getOccupancy100());
231         curAtom.resNumIns = "" + curAtom.resNumber + curAtom.insCode;
232         curAtom.tfactor = atom.getBfactor100() / 100f;
233         curAtom.type = 0;
234         significantAtoms.add(curAtom);
235       }
236     }
237     return significantAtoms;
238   }
239
240   private void createAnnotation(SequenceI sequence, PDBChain chain,
241           org.jmol.modelset.Atom[] jmolAtoms)
242   {
243     char[] secstr = new char[sequence.getLength()];
244     char[] secstrcode = new char[sequence.getLength()];
245
246     // Ensure Residue size equals Seq size
247     if (chain.residues.size() != sequence.getLength())
248     {
249       return;
250     }
251     int annotIndex = 0;
252     for (Residue residue : chain.residues)
253     {
254       Atom repAtom = residue.getAtoms().get(0);
255       STR proteinStructureSubType = jmolAtoms[repAtom.atomIndex].group
256               .getProteinStructureSubType();
257       setSecondaryStructure(proteinStructureSubType, annotIndex, secstr,
258               secstrcode);
259       ++annotIndex;
260     }
261     addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
262             secstrcode, chain.id, sequence.getStart());
263   }
264
265   /**
266    * Helper method that adds an AlignmentAnnotation for secondary structure to
267    * the sequence, provided at least one secondary structure prediction has been
268    * made
269    * 
270    * @param modelTitle
271    * @param seq
272    * @param secstr
273    * @param secstrcode
274    * @param chainId
275    * @param firstResNum
276    * @return
277    */
278   protected void addSecondaryStructureAnnotation(String modelTitle,
279           SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
280           int firstResNum)
281   {
282     char[] seq = sq.getSequence();
283     boolean ssFound = false;
284     Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1];
285     for (int p = 0; p < seq.length; p++)
286     {
287       if (secstr[p] >= 'A' && secstr[p] <= 'z')
288       {
289         try
290         {
291         asecstr[p] = new Annotation(String.valueOf(secstr[p]), null,
292                 secstrcode[p], Float.NaN);
293         ssFound = true;
294         } catch (Exception e)
295         {
296           // e.printStackTrace();
297         }
298       }
299     }
300
301     if (ssFound)
302     {
303       String mt = modelTitle == null ? getDataName() : modelTitle;
304       mt += chainId;
305       AlignmentAnnotation ann = new AlignmentAnnotation(
306               "Secondary Structure", "Secondary Structure for " + mt,
307               asecstr);
308       ann.belowAlignment = true;
309       ann.visible = true;
310       ann.autoCalculated = false;
311       ann.setCalcId(getClass().getName());
312       ann.adjustForAlignment();
313       ann.validateRangeAndDisplay();
314       annotations.add(ann);
315       sq.addAlignmentAnnotation(ann);
316     }
317   }
318
319   private void waitForScript(Viewer jmd)
320   {
321     while (jmd.isScriptExecuting())
322     {
323       try
324       {
325         Thread.sleep(50);
326
327       } catch (InterruptedException x)
328       {
329       }
330     }
331   }
332
333   /**
334    * Convert Jmol's secondary structure code to Jalview's, and stored it in the
335    * secondary structure arrays at the given sequence position
336    * 
337    * @param proteinStructureSubType
338    * @param pos
339    * @param secstr
340    * @param secstrcode
341    */
342   protected void setSecondaryStructure(STR proteinStructureSubType,
343           int pos, char[] secstr, char[] secstrcode)
344   {
345     switch (proteinStructureSubType)
346     {
347     case HELIX310:
348       secstr[pos] = '3';
349       break;
350     case HELIX:
351     case HELIXALPHA:
352       secstr[pos] = 'H';
353       break;
354     case HELIXPI:
355       secstr[pos] = 'P';
356       break;
357     case SHEET:
358       secstr[pos] = 'E';
359       break;
360     default:
361       secstr[pos] = 0;
362     }
363
364     switch (proteinStructureSubType)
365     {
366     case HELIX310:
367     case HELIXALPHA:
368     case HELIXPI:
369     case HELIX:
370       secstrcode[pos] = 'H';
371       break;
372     case SHEET:
373       secstrcode[pos] = 'E';
374       break;
375     default:
376       secstrcode[pos] = 0;
377     }
378   }
379
380   /**
381    * Convert any non-standard peptide codes to their standard code table
382    * equivalent. (Initial version only does Selenomethionine MSE->MET.)
383    * 
384    * @param threeLetterCode
385    * @param seq
386    * @param pos
387    */
388   protected void replaceNonCanonicalResidue(String threeLetterCode,
389           char[] seq, int pos)
390   {
391     String canonical = ResidueProperties
392             .getCanonicalAminoAcid(threeLetterCode);
393     if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
394     {
395       seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
396     }
397   }
398
399   /**
400    * Not implemented - returns null
401    */
402   @Override
403   public String print()
404   {
405     return null;
406   }
407
408   /**
409    * Not implemented
410    */
411   @Override
412   public void setCallbackFunction(String callbackType,
413           String callbackFunction)
414   {
415   }
416
417   @Override
418   public void notifyCallback(CBK cbType, Object[] data)
419   {
420     String strInfo = (data == null || data[1] == null ? null : data[1]
421             .toString());
422     switch (cbType)
423     {
424     case ECHO:
425       sendConsoleEcho(strInfo);
426       break;
427     case SCRIPT:
428       notifyScriptTermination((String) data[2],
429               ((Integer) data[3]).intValue());
430       break;
431     case MEASURE:
432       String mystatus = (String) data[3];
433       if (mystatus.indexOf("Picked") >= 0
434               || mystatus.indexOf("Sequence") >= 0)
435       {
436         // Picking mode
437         sendConsoleMessage(strInfo);
438       }
439       else if (mystatus.indexOf("Completed") >= 0)
440       {
441         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
442                 strInfo.length() - 1));
443       }
444       break;
445     case MESSAGE:
446       sendConsoleMessage(data == null ? null : strInfo);
447       break;
448     case PICK:
449       sendConsoleMessage(strInfo);
450       break;
451     default:
452       break;
453     }
454   }
455
456   String lastConsoleEcho = "";
457
458   private void sendConsoleEcho(String string)
459   {
460     lastConsoleEcho += string;
461     lastConsoleEcho += "\n";
462   }
463
464   String lastConsoleMessage = "";
465
466   private void sendConsoleMessage(String string)
467   {
468     lastConsoleMessage += string;
469     lastConsoleMessage += "\n";
470   }
471
472   int lastScriptTermination = -1;
473
474   String lastScriptMessage = "";
475
476   private void notifyScriptTermination(String string, int intValue)
477   {
478     lastScriptMessage += string;
479     lastScriptMessage += "\n";
480     lastScriptTermination = intValue;
481   }
482
483   @Override
484   public boolean notifyEnabled(CBK callbackPick)
485   {
486     switch (callbackPick)
487     {
488     case MESSAGE:
489     case SCRIPT:
490     case ECHO:
491     case LOADSTRUCT:
492     case ERROR:
493       return true;
494     default:
495       return false;
496     }
497   }
498
499   /**
500    * Not implemented - returns null
501    */
502   @Override
503   public String eval(String strEval)
504   {
505     return null;
506   }
507
508   /**
509    * Not implemented - returns null
510    */
511   @Override
512   public float[][] functionXY(String functionName, int x, int y)
513   {
514     return null;
515   }
516
517   /**
518    * Not implemented - returns null
519    */
520   @Override
521   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
522   {
523     return null;
524   }
525
526   /**
527    * Not implemented - returns null
528    */
529   @Override
530   public String createImage(String fileName, String imageType,
531           Object text_or_bytes, int quality)
532   {
533     return null;
534   }
535
536   /**
537    * Not implemented - returns null
538    */
539   @Override
540   public Map<String, Object> getRegistryInfo()
541   {
542     return null;
543   }
544
545   /**
546    * Not implemented
547    */
548   @Override
549   public void showUrl(String url)
550   {
551   }
552
553   /**
554    * Not implemented - returns null
555    */
556   @Override
557   public Dimension resizeInnerPanel(String data)
558   {
559     return null;
560   }
561
562   @Override
563   public Map<String, Object> getJSpecViewProperty(String arg0)
564   {
565     return null;
566   }
567
568   public boolean isPredictSecondaryStructure()
569   {
570     return predictSecondaryStructure;
571   }
572
573   public void setPredictSecondaryStructure(boolean predictSecondaryStructure)
574   {
575     this.predictSecondaryStructure = predictSecondaryStructure;
576   }
577
578   public boolean isVisibleChainAnnotation()
579   {
580     return visibleChainAnnotation;
581   }
582
583   public void setVisibleChainAnnotation(boolean visibleChainAnnotation)
584   {
585     this.visibleChainAnnotation = visibleChainAnnotation;
586   }
587
588 }