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