8d76afc720bbfb556065587ed86e0e7438b2a9f3
[jalview.git] / src / jalview / ext / jmol / PDBFileWithJmol.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.PDBEntry;
26 import jalview.datamodel.Sequence;
27 import jalview.datamodel.SequenceI;
28 import jalview.io.AlignFile;
29 import jalview.io.FileParse;
30 import jalview.schemes.ResidueProperties;
31 import jalview.util.MessageManager;
32
33 import java.io.IOException;
34 import java.util.Hashtable;
35 import java.util.Map;
36
37 import javajs.awt.Dimension;
38
39 import org.jmol.api.JmolStatusListener;
40 import org.jmol.api.JmolViewer;
41 import org.jmol.c.CBK;
42 import org.jmol.modelset.Group;
43 import org.jmol.modelset.Model;
44 import org.jmol.modelset.ModelSet;
45 import org.jmol.modelsetbio.BioModel;
46 import org.jmol.modelsetbio.BioPolymer;
47 import org.jmol.viewer.Viewer;
48
49 /**
50  * Import and process PDB files with Jmol
51  * 
52  * @author jprocter
53  * 
54  */
55 public class PDBFileWithJmol extends AlignFile implements
56         JmolStatusListener
57 {
58   Viewer viewer = null;
59
60   public PDBFileWithJmol(String inFile, String type) throws IOException
61   {
62     super(inFile, type);
63   }
64
65   public PDBFileWithJmol(FileParse fp) throws IOException
66   {
67     super(fp);
68   }
69
70   public PDBFileWithJmol()
71   {
72   }
73
74   /**
75    * create a headless jmol instance for dataprocessing
76    * 
77    * @return
78    */
79   private Viewer getJmolData()
80   {
81     if (viewer == null)
82     {
83       try
84       {
85         viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null,
86                 null, "-x -o -n", this);
87       } catch (ClassCastException x)
88       {
89         throw new Error(MessageManager.formatMessage(
90                 "error.jmol_version_not_compatible_with_jalview_version",
91                 new String[] { JmolViewer.getJmolVersion() }), x);
92       }
93     }
94     return viewer;
95   }
96
97   private void waitForScript(Viewer jmd)
98   {
99     while (jmd.isScriptExecuting())
100     {
101       try
102       {
103         Thread.sleep(50);
104
105       } catch (InterruptedException x)
106       {
107       }
108     }
109   }
110
111   /*
112    * (non-Javadoc)
113    * 
114    * @see jalview.io.AlignFile#parse()
115    */
116   @Override
117   public void parse() throws IOException
118   {
119     Viewer jmd = getJmolData();
120     jmd.openReader(getDataName(), getDataName(), getReader());
121     waitForScript(jmd);
122
123     if (jmd.ms.mc > 0)
124     {
125       ModelSet ms = jmd.ms;
126       // Jmol 14.2 added third argument doReport = false
127       ms.calculateStructures(null, true, false, false, true);
128       // System.out.println("Structs\n"+structs);
129       Group group = null;
130       int modelIndex = -1;
131       for (Model model : ms.am)
132       {
133         modelIndex++;
134         for (BioPolymer bp : ((BioModel) model).bioPolymers)
135         {
136           int lastChainId = 0; // int value of character e.g. 65 for A
137           String lastChainIdAlpha = "";
138
139           int[] groups = bp.getLeadAtomIndices();
140           char seq[] = new char[groups.length], secstr[] = new char[groups.length], secstrcode[] = new char[groups.length];
141           int groupc = 0, len = 0, firstrnum = 1, lastrnum = 0;
142
143           do
144           {
145             if (groupc >= groups.length
146                     || ms.at[groups[groupc]].group.chain.chainID != lastChainId)
147             {
148               /*
149                * on change of chain (or at end), construct the sequence and
150                * secondary structure annotation for the last chain
151                */
152               if (len > 0)
153               {
154                 boolean isNa = bp.isNucleic();
155                 // normalise sequence from Jmol to jalview
156                 int[] cinds = isNa ? ResidueProperties.nucleotideIndex
157                         : ResidueProperties.aaIndex;
158                 int nonGap = isNa ? ResidueProperties.maxNucleotideIndex
159                         : ResidueProperties.maxProteinIndex;
160                 char ngc = 'X';
161                 char newseq[] = new char[len];
162                 Annotation asecstr[] = new Annotation[len + firstrnum - 1];
163                 for (int p = 0; p < len; p++)
164                 {
165                   newseq[p] = cinds[seq[p]] == nonGap ? ngc : seq[p];
166                   if (secstr[p] >= 'A' && secstr[p] <= 'z')
167                   {
168                     try
169                     {
170                       asecstr[p] = new Annotation("" + secstr[p], null,
171                               secstrcode[p], Float.NaN);
172                     } catch (ArrayIndexOutOfBoundsException e)
173                     {
174                       // skip - patch for JAL-1836
175                     }
176                   }
177                 }
178                 String modelTitle = (String) ms
179                         .getInfo(modelIndex, "title");
180                 SequenceI sq = new Sequence("" + getDataName() + "|"
181                         + modelTitle + "|" + lastChainIdAlpha, newseq,
182                         firstrnum, lastrnum);
183                 PDBEntry pdbe = new PDBEntry();
184                 pdbe.setFile(getDataName());
185                 pdbe.setId(getDataName());
186                 pdbe.setProperty(new Hashtable());
187                 // pdbe.getProperty().put("CHAIN", "" + _lastChainId);
188                 pdbe.setChainCode(lastChainIdAlpha);
189                 sq.addPDBId(pdbe);
190                 // JAL-1533
191                 // Need to put the number of models for this polymer somewhere
192                 // for Chimera/others to grab
193                 // pdbe.getProperty().put("PDBMODELS", biopoly.)
194                 seqs.add(sq);
195                 if (!isNa)
196                 {
197                   String mt = modelTitle == null ? getDataName()
198                           : modelTitle;
199                   if (lastChainId >= ' ')
200                   {
201                     mt += lastChainIdAlpha;
202                   }
203                   AlignmentAnnotation ann = new AlignmentAnnotation(
204                           "Secondary Structure", "Secondary Structure for "
205                                   + mt, asecstr);
206                   ann.belowAlignment = true;
207                   ann.visible = true;
208                   ann.autoCalculated = false;
209                   ann.setCalcId(getClass().getName());
210                   sq.addAlignmentAnnotation(ann);
211                   ann.adjustForAlignment();
212                   ann.validateRangeAndDisplay();
213                   annotations.add(ann);
214                 }
215               }
216               len = 0;
217               firstrnum = 1;
218               lastrnum = 0;
219             }
220             if (groupc < groups.length)
221             {
222               group = ms.at[groups[groupc]].group;
223               if (len == 0)
224               {
225                 firstrnum = group.getResno();
226                 lastChainId = group.chain.chainID;
227                 lastChainIdAlpha = group.chain.getIDStr();
228               }
229               else
230               {
231                 lastrnum = group.getResno();
232               }
233               seq[len] = group.getGroup1();
234
235               /*
236                * JAL-1828 replace a modified amino acid with its standard
237                * equivalent (e.g. MSE with MET->M) to maximise sequence matching
238                */
239               String threeLetterCode = group.getGroup3();
240               String canonical = ResidueProperties
241                       .getCanonicalAminoAcid(threeLetterCode);
242               if (canonical != null
243                       && !canonical.equalsIgnoreCase(threeLetterCode))
244               {
245                 seq[len] = ResidueProperties
246                         .getSingleCharacterCode(canonical);
247               }
248               switch (group.getProteinStructureSubType())
249               {
250               case HELIX310:
251                 if (secstr[len] == 0)
252                 {
253                   secstr[len] = '3';
254                 }
255               case HELIXALPHA:
256                 if (secstr[len] == 0)
257                 {
258                   secstr[len] = 'H';
259                 }
260               case HELIXPI:
261                 if (secstr[len] == 0)
262                 {
263                   secstr[len] = 'P';
264                 }
265               case HELIX:
266                 if (secstr[len] == 0)
267                 {
268                   secstr[len] = 'H';
269                 }
270                 secstrcode[len] = 'H';
271                 break;
272               case SHEET:
273                 secstr[len] = 'E';
274                 secstrcode[len] = 'E';
275                 break;
276               default:
277                 secstr[len] = 0;
278                 secstrcode[len] = 0;
279               }
280               len++;
281             }
282           } while (groupc++ < groups.length);
283         }
284       }
285
286       /*
287        * lastScriptTermination = -9465; String dsspOut =
288        * jmd.evalString("calculate STRUCTURE"); if (dsspOut.equals("pending")) {
289        * while (lastScriptTermination == -9465) { try { Thread.sleep(50); }
290        * catch (Exception x) { } ; } } System.out.println(lastConsoleEcho);
291        */
292     }
293   }
294
295   /*
296    * (non-Javadoc)
297    * 
298    * @see jalview.io.AlignFile#print()
299    */
300   @Override
301   public String print()
302   {
303     // TODO Auto-generated method stub
304     return null;
305   }
306
307   @Override
308   public void setCallbackFunction(String callbackType,
309           String callbackFunction)
310   {
311     // TODO Auto-generated method stub
312
313   }
314
315   /*
316    * @Override public void notifyCallback(EnumCallback type, Object[] data) {
317    * try { switch (type) { case ERROR: case SCRIPT:
318    * notifyScriptTermination((String) data[2], ((Integer) data[3]).intValue());
319    * break; case MESSAGE: sendConsoleMessage((data == null) ? ((String) null) :
320    * (String) data[1]); break; case LOADSTRUCT: notifyFileLoaded((String)
321    * data[1], (String) data[2], (String) data[3], (String) data[4], ((Integer)
322    * data[5]).intValue());
323    * 
324    * break; default: // System.err.println("Unhandled callback " + type + " " //
325    * + data[1].toString()); break; } } catch (Exception e) {
326    * System.err.println("Squashed Jmol callback handler error:");
327    * e.printStackTrace(); } }
328    */
329   public void notifyCallback(CBK type, Object[] data)
330   {
331     String strInfo = (data == null || data[1] == null ? null : data[1]
332             .toString());
333     switch (type)
334     {
335     case ECHO:
336       sendConsoleEcho(strInfo);
337       break;
338     case SCRIPT:
339       notifyScriptTermination((String) data[2],
340               ((Integer) data[3]).intValue());
341       break;
342     case MEASURE:
343       String mystatus = (String) data[3];
344       if (mystatus.indexOf("Picked") >= 0
345               || mystatus.indexOf("Sequence") >= 0)
346       {
347         // Picking mode
348         sendConsoleMessage(strInfo);
349       }
350       else if (mystatus.indexOf("Completed") >= 0)
351       {
352         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
353                 strInfo.length() - 1));
354       }
355       break;
356     case MESSAGE:
357       sendConsoleMessage(data == null ? null : strInfo);
358       break;
359     case PICK:
360       sendConsoleMessage(strInfo);
361       break;
362     default:
363       break;
364     }
365   }
366
367   String lastConsoleEcho = "";
368
369   private void sendConsoleEcho(String string)
370   {
371     lastConsoleEcho += string;
372     lastConsoleEcho += "\n";
373   }
374
375   String lastConsoleMessage = "";
376
377   private void sendConsoleMessage(String string)
378   {
379     lastConsoleMessage += string;
380     lastConsoleMessage += "\n";
381   }
382
383   int lastScriptTermination = -1;
384
385   String lastScriptMessage = "";
386
387   private void notifyScriptTermination(String string, int intValue)
388   {
389     lastScriptMessage += string;
390     lastScriptMessage += "\n";
391     lastScriptTermination = intValue;
392   }
393
394   @Override
395   public boolean notifyEnabled(CBK callbackPick)
396   {
397     switch (callbackPick)
398     {
399     case MESSAGE:
400     case SCRIPT:
401     case ECHO:
402     case LOADSTRUCT:
403     case ERROR:
404       return true;
405     default:
406       return false;
407     }
408   }
409
410   @Override
411   public String eval(String strEval)
412   {
413     // TODO Auto-generated method stub
414     return null;
415   }
416
417   @Override
418   public float[][] functionXY(String functionName, int x, int y)
419   {
420     // TODO Auto-generated method stub
421     return null;
422   }
423
424   @Override
425   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
426   {
427     // TODO Auto-generated method stub
428     return null;
429   }
430
431   @Override
432   public String createImage(String fileName, String type,
433           Object text_or_bytes, int quality)
434   {
435     // TODO Auto-generated method stub
436     return null;
437   }
438
439   @Override
440   public Map<String, Object> getRegistryInfo()
441   {
442     // TODO Auto-generated method stub
443     return null;
444   }
445
446   @Override
447   public void showUrl(String url)
448   {
449     // TODO Auto-generated method stub
450
451   }
452
453   @Override
454   public Dimension resizeInnerPanel(String data)
455   {
456     return null;
457   }
458
459   @Override
460   public Map<String, Object> getJSpecViewProperty(String arg0)
461   {
462     return null;
463   }
464
465 }