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