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