JAL-1919 PDBfile and JmolParser refactor
[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.DBRefEntry;
26 import jalview.datamodel.DBRefSource;
27 import jalview.datamodel.PDBEntry;
28 import jalview.datamodel.Sequence;
29 import jalview.datamodel.SequenceI;
30 import jalview.io.FileParse;
31 import jalview.io.StructureFile;
32 import jalview.schemes.ResidueProperties;
33 import jalview.util.Comparison;
34 import jalview.util.MessageManager;
35
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.Hashtable;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Vector;
42
43 import javajs.awt.Dimension;
44
45 import org.jmol.api.JmolStatusListener;
46 import org.jmol.api.JmolViewer;
47 import org.jmol.c.CBK;
48 import org.jmol.c.STR;
49 import org.jmol.modelset.Group;
50 import org.jmol.modelset.Model;
51 import org.jmol.modelset.ModelSet;
52 import org.jmol.modelsetbio.BioModel;
53 import org.jmol.modelsetbio.BioPolymer;
54 import org.jmol.modelsetbio.Monomer;
55 import org.jmol.viewer.Viewer;
56
57 import MCview.Atom;
58 import MCview.PDBChain;
59 import MCview.Residue;
60
61 /**
62  * Import and process files with Jmol for file like PDB, mmCIF
63  * 
64  * @author jprocter
65  * 
66  */
67 public class JmolParser extends StructureFile implements JmolStatusListener
68 {
69   Viewer viewer = null;
70
71   public JmolParser(boolean addAlignmentAnnotations,
72           boolean predictSecondaryStructure, boolean externalSecStr,
73           String inFile, String type) throws IOException
74   {
75     super(inFile, type);
76     this.visibleChainAnnotation = addAlignmentAnnotations;
77     this.predictSecondaryStructure = predictSecondaryStructure;
78     this.externalSecondaryStructure = externalSecStr;
79   }
80
81   public JmolParser(boolean addAlignmentAnnotations,
82           boolean predictSecondaryStructure, boolean externalSecStr,
83           FileParse fp) throws IOException
84   {
85     super(fp);
86     this.visibleChainAnnotation = addAlignmentAnnotations;
87     this.predictSecondaryStructure = predictSecondaryStructure;
88     this.externalSecondaryStructure = externalSecStr;
89   }
90
91   public JmolParser(FileParse fp) throws IOException
92   {
93     super(fp);
94   }
95
96   public JmolParser(String inFile, String type) throws IOException
97   {
98     super(inFile, type);
99   }
100
101   public JmolParser()
102   {
103   }
104
105   /**
106    * Calls the Jmol library to parse the PDB/mmCIF file, and then inspects the
107    * resulting object model to generate Jalview-style sequences, with secondary
108    * structure annotation added where available (i.e. where it has been computed
109    * by Jmol using DSSP).
110    * 
111    * @see jalview.io.AlignFile#parse()
112    */
113   @Override
114   public void parse() throws IOException
115   {
116
117     setChains(new Vector<PDBChain>());
118     Viewer jmolModel = getJmolData();
119     jmolModel.openReader(getDataName(), getDataName(), getReader());
120     waitForScript(jmolModel);
121
122     /*
123      * Convert one or more Jmol Model objects to Jalview sequences
124      */
125     if (jmolModel.ms.mc > 0)
126     {
127       parseBiopolymer(jmolModel.ms);
128       // transformJmolModelToJalview(jmolModel.ms);
129     }
130   }
131
132   /**
133    * create a headless jmol instance for dataprocessing
134    * 
135    * @return
136    */
137   private Viewer getJmolData()
138   {
139     if (viewer == null)
140     {
141       try
142       {
143         viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null,
144                 null, "-x -o -n", this);
145         // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used
146         viewer.setBooleanProperty("defaultStructureDSSP", true);
147       } catch (ClassCastException x)
148       {
149         throw new Error(MessageManager.formatMessage(
150                 "error.jmol_version_not_compatible_with_jalview_version",
151                 new String[] { JmolViewer.getJmolVersion() }), x);
152       }
153     }
154     return viewer;
155   }
156
157   public void transformJmolModelToJalview(ModelSet ms) throws IOException
158   {
159     try
160     {
161       String lastID = "";
162       List<SequenceI> rna = new ArrayList<SequenceI>();
163       List<SequenceI> prot = new ArrayList<SequenceI>();
164       PDBChain tmpchain;
165       String pdbId = (String) ms.getInfo(0, "title");
166       setId(pdbId);
167       List<Atom> significantAtoms = convertSignificantAtoms(ms);
168       for (Atom tmpatom : significantAtoms)
169       {
170         try
171         {
172           tmpchain = findChain(tmpatom.chain);
173           if (tmpatom.resNumIns.trim().equals(lastID))
174           {
175             // phosphorylated protein - seen both CA and P..
176             continue;
177           }
178           tmpchain.atoms.addElement(tmpatom);
179         } catch (Exception e)
180         {
181           tmpchain = new PDBChain(pdbId, tmpatom.chain);
182           getChains().add(tmpchain);
183           tmpchain.atoms.addElement(tmpatom);
184         }
185         lastID = tmpatom.resNumIns.trim();
186       }
187       makeResidueList();
188       makeCaBondList();
189
190       if (getId() == null)
191       {
192         setId(inFile.getName());
193       }
194       for (PDBChain chain : getChains())
195       {
196         SequenceI chainseq = postProcessChain(chain);
197         createAnnotation(chainseq, chain, ms.at);
198         if (isRNA(chainseq))
199         {
200           rna.add(chainseq);
201         }
202         else
203         {
204           prot.add(chainseq);
205         }
206       }
207     } catch (OutOfMemoryError er)
208     {
209       System.out
210               .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
211       throw new IOException(
212               MessageManager
213                       .getString("exception.outofmemory_loading_mmcif_file"));
214     }
215   }
216
217   private List<Atom> convertSignificantAtoms(ModelSet ms)
218   {
219     List<Atom> significantAtoms = new ArrayList<Atom>();
220     for (org.jmol.modelset.Atom atom : ms.at)
221     {
222       if (atom.getAtomName().equalsIgnoreCase("CA")
223               || atom.getAtomName().equalsIgnoreCase("P"))
224       {
225         Atom curAtom = new Atom(atom.x, atom.y, atom.z);
226         curAtom.atomIndex = atom.getIndex();
227         curAtom.chain = atom.getChainIDStr();
228         curAtom.insCode = atom.group.getInsertionCode();
229         curAtom.name = atom.getAtomName();
230         curAtom.number = atom.getAtomNumber();
231         curAtom.resName = atom.getGroup3(true);
232         curAtom.resNumber = atom.getResno();
233         curAtom.ss = getSecondayStructure(atom.group
234                 .getProteinStructureSubType());
235         curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom
236                 .getIndex()] : Float.valueOf(atom.getOccupancy100());
237         curAtom.resNumIns = "" + curAtom.resNumber + curAtom.insCode;
238         // curAtom.tfactor = atom.group.;
239         curAtom.type = 0;
240         significantAtoms.add(curAtom);
241       }
242     }
243     return significantAtoms;
244   }
245
246   private void createAnnotation(SequenceI sequence, PDBChain chain,
247           org.jmol.modelset.Atom[] jmolAtoms)
248   {
249     char[] secstr = new char[sequence.getLength()];
250     char[] secstrcode = new char[sequence.getLength()];
251     for (Residue residue : chain.residues)
252     {
253
254     }
255     addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
256             secstrcode, chain.id, sequence.getStart());
257   }
258
259   /**
260    * Process the Jmol BioPolymer array and generate a Jalview sequence for each
261    * chain found (including any secondary structure annotation from DSSP)
262    * 
263    * @param ms
264    * @throws IOException
265    */
266   public void parseBiopolymer(ModelSet ms) throws IOException
267   {
268     int modelIndex = -1;
269     for (Model model : ms.am)
270     {
271       modelIndex++;
272       String modelTitle = (String) ms.getInfo(modelIndex, "title");
273       /*
274        * Chains can span BioPolymers, so first make a flattened list, and then
275        * work out the lengths of chains present
276        */
277       List<Monomer> monomers = getMonomers(ms, (BioModel) model);
278       List<Integer> chainLengths = getChainLengths(monomers);
279
280       /*
281        * now chop up the Monomer list to make Jalview Sequences
282        */
283       int from = 0;
284       for (int length : chainLengths)
285       {
286         buildSequenceFromChain(monomers.subList(from, from + length),
287                 modelTitle);
288         from += length;
289       }
290     }
291   }
292
293   /**
294    * Returns a flattened list of Monomer (residues) in order, across all
295    * BioPolymers in the model. This simplifies assembling chains which span
296    * BioPolymers. The result omits any alternate residues reported for the same
297    * sequence position (RESNUM value).
298    * 
299    * @param ms
300    * @param model
301    * @return
302    */
303   protected List<Monomer> getMonomers(ModelSet ms, BioModel model)
304   {
305     List<Monomer> result = new ArrayList<Monomer>();
306     int lastResNo = Integer.MIN_VALUE;
307
308     for (BioPolymer bp : model.bioPolymers)
309     {
310       for (int groupLeadAtoms : bp.getLeadAtomIndices())
311       {
312         Group group = ms.at[groupLeadAtoms].group;
313         if (group instanceof Monomer)
314         {
315           /*
316            * ignore alternate residue at same position example: 1ejg has
317            * residues A:LEU, B:ILE at RESNUM=25
318            */
319           int resNo = group.getResno();
320           if (lastResNo != resNo)
321           {
322             result.add((Monomer) group);
323           }
324           lastResNo = resNo;
325         }
326       }
327     }
328     return result;
329   }
330
331   /**
332    * Scans the list of Monomers (residue models), inspecting the chain id for
333    * each, and returns an array whose length is the number of chains, and values
334    * the length of each chain
335    * 
336    * @param monomers
337    * @return
338    */
339   protected List<Integer> getChainLengths(List<Monomer> monomers)
340   {
341     List<Integer> chainLengths = new ArrayList<Integer>();
342     int lastChainId = -1;
343     int length = 0;
344
345     for (Monomer monomer : monomers)
346     {
347       int chainId = monomer.chain.chainID;
348       if (chainId != lastChainId && length > 0)
349       {
350         /*
351          * change of chain - record the length of the last one
352          */
353         chainLengths.add(length);
354         length = 0;
355       }
356       lastChainId = chainId;
357       length++;
358     }
359     if (length > 0)
360     {
361       /*
362        * record the length of the final chain
363        */
364       chainLengths.add(length);
365     }
366
367     return chainLengths;
368   }
369
370   /**
371    * Helper method to construct a sequence for one chain and add it to the seqs
372    * list
373    * 
374    * @param monomers
375    *          a list of all monomers in the chain
376    * @param modelTitle
377    */
378   protected void buildSequenceFromChain(List<Monomer> monomers,
379           String modelTitle)
380   {
381     final int length = monomers.size();
382
383     /*
384      * arrays to hold sequence and secondary structure
385      */
386     char[] seq = new char[length];
387     char[] secstr = new char[length];
388     char[] secstrcode = new char[length];
389
390     /*
391      * populate the sequence and secondary structure arrays
392      */
393     extractJmolChainData(monomers, seq, secstr, secstrcode);
394
395     /*
396      * grab chain code and start position from first residue;
397      */
398     String chainId = monomers.get(0).chain.getIDStr();
399     int firstResNum = monomers.get(0).getResno();
400     if (firstResNum < 1)
401     {
402       // Jalview doesn't like residue < 1, so force this to 1
403       System.err.println("Converting chain " + chainId + " first RESNUM ("
404               + firstResNum + ") to 1");
405       firstResNum = 1;
406     }
407
408     /*
409      * convert any non-gap unknown residues to 'X'
410      */
411     convertNonGapCharacters(seq);
412
413     /*
414      * construct and add the Jalview sequence
415      */
416     String seqName = "" + modelTitle + "|" + chainId;
417     int start = firstResNum;
418     int end = firstResNum + length - 1;
419
420     SequenceI sq = new Sequence(seqName, seq, start, end);
421
422     addPdbid(sq, modelTitle, chainId);
423
424     addSourceDBref(sq, modelTitle, start, end);
425
426     seqs.add(sq);
427
428     /*
429      * add secondary structure predictions (if any)
430      */
431       addSecondaryStructureAnnotation(modelTitle, sq, secstr, secstrcode,
432               chainId, firstResNum);
433
434   }
435
436   /**
437    * Scans the list of (Jmol) Monomer objects, and adds the residue for each to
438    * the sequence array, and any converted secondary structure prediction to the
439    * secondary structure arrays
440    * 
441    * @param monomers
442    * @param seq
443    * @param secstr
444    * @param secstrcode
445    */
446   protected void extractJmolChainData(List<Monomer> monomers, char[] seq,
447           char[] secstr, char[] secstrcode)
448   {
449     int pos = 0;
450     for (Monomer monomer : monomers)
451     {
452       seq[pos] = monomer.getGroup1();
453
454       /*
455        * JAL-1828 replace a modified amino acid with its standard equivalent
456        * (e.g. MSE with MET->M) to maximise sequence matching
457        */
458       replaceNonCanonicalResidue(monomer.getGroup3(), seq, pos);
459
460       /*
461        * if Jmol has derived a secondary structure prediction for this position,
462        * convert it to Jalview equivalent and save it
463        */
464       setSecondaryStructure(monomer.getProteinStructureSubType(), pos,
465               secstr, secstrcode);
466       pos++;
467     }
468   }
469
470   /**
471    * Replace any non-gap miscellaneous characters with 'X'
472    * 
473    * @param seq
474    * @return
475    */
476   protected void convertNonGapCharacters(char[] seq)
477   {
478     boolean isNa = Comparison.areNucleotide(new char[][] { seq });
479     int[] cinds = isNa ? ResidueProperties.nucleotideIndex
480             : ResidueProperties.aaIndex;
481     int nonGap = isNa ? ResidueProperties.maxNucleotideIndex
482             : ResidueProperties.maxProteinIndex;
483
484     for (int p = 0; p < seq.length; p++)
485     {
486       if (cinds[seq[p]] == nonGap)
487       {
488         seq[p] = 'X';
489       }
490     }
491   }
492
493   /**
494    * Add a source db ref entry for the given sequence.
495    * 
496    * @param sq
497    * @param accessionId
498    * @param start
499    * @param end
500    */
501   protected void addSourceDBref(SequenceI sq, String accessionId,
502           int start, int end)
503   {
504     DBRefEntry sourceDBRef = new DBRefEntry();
505     sourceDBRef.setAccessionId(accessionId);
506     sourceDBRef.setSource(DBRefSource.MMCIF);
507     sourceDBRef.setStartRes(start);
508     sourceDBRef.setEndRes(end);
509     sq.setSourceDBRef(sourceDBRef);
510     sq.addDBRef(sourceDBRef);
511   }
512
513   /**
514    * Add a PDBEntry giving the source of PDB data to the sequence
515    * 
516    * @param sq
517    * @param id
518    * @param chainId
519    */
520   protected void addPdbid(SequenceI sq, String id, String chainId)
521   {
522     PDBEntry entry = new PDBEntry();
523     entry.setId(id);
524     entry.setType(PDBEntry.Type.MMCIF);
525     entry.setProperty(new Hashtable());
526     if (chainId != null)
527     {
528       // entry.getProperty().put("CHAIN", chains.elementAt(i).id);
529       entry.setChainCode(String.valueOf(chainId));
530     }
531     if (inFile != null)
532     {
533       entry.setFile(inFile.getAbsolutePath());
534     }
535     else
536     {
537       // TODO: decide if we should dump the datasource to disk
538       entry.setFile(getDataName());
539     }
540
541     sq.addPDBId(entry);
542   }
543
544
545   /**
546    * Helper method that adds an AlignmentAnnotation for secondary structure to
547    * the sequence, provided at least one secondary structure prediction has been
548    * made
549    * 
550    * @param modelTitle
551    * @param seq
552    * @param secstr
553    * @param secstrcode
554    * @param chainId
555    * @param firstResNum
556    * @return
557    */
558   protected void addSecondaryStructureAnnotation(String modelTitle,
559           SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
560           int firstResNum)
561   {
562     char[] seq = sq.getSequence();
563     boolean ssFound = false;
564     Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1];
565     for (int p = 0; p < seq.length; p++)
566     {
567       if (secstr[p] >= 'A' && secstr[p] <= 'z')
568       {
569         asecstr[p] = new Annotation(String.valueOf(secstr[p]), null,
570                 secstrcode[p], Float.NaN);
571         ssFound = true;
572       }
573     }
574
575     if (ssFound)
576     {
577       String mt = modelTitle == null ? getDataName() : modelTitle;
578       mt += chainId;
579       AlignmentAnnotation ann = new AlignmentAnnotation(
580               "Secondary Structure", "Secondary Structure for " + mt,
581               asecstr);
582       ann.belowAlignment = true;
583       ann.visible = true;
584       ann.autoCalculated = false;
585       ann.setCalcId(getClass().getName());
586       ann.adjustForAlignment();
587       ann.validateRangeAndDisplay();
588       annotations.add(ann);
589       sq.addAlignmentAnnotation(ann);
590     }
591   }
592
593   private void waitForScript(Viewer jmd)
594   {
595     while (jmd.isScriptExecuting())
596     {
597       try
598       {
599         Thread.sleep(50);
600
601       } catch (InterruptedException x)
602       {
603       }
604     }
605   }
606
607   /**
608    * Convert Jmol's secondary structure code to Jalview's, and stored it in the
609    * secondary structure arrays at the given sequence position
610    * 
611    * @param proteinStructureSubType
612    * @param pos
613    * @param secstr
614    * @param secstrcode
615    */
616   protected void setSecondaryStructure(STR proteinStructureSubType,
617           int pos, char[] secstr, char[] secstrcode)
618   {
619     switch (proteinStructureSubType)
620     {
621     case HELIX310:
622       secstr[pos] = '3';
623       break;
624     case HELIX:
625     case HELIXALPHA:
626       secstr[pos] = 'H';
627       break;
628     case HELIXPI:
629       secstr[pos] = 'P';
630       break;
631     case SHEET:
632       secstr[pos] = 'E';
633       break;
634     default:
635       secstr[pos] = 0;
636     }
637
638     switch (proteinStructureSubType)
639     {
640     case HELIX310:
641     case HELIXALPHA:
642     case HELIXPI:
643     case HELIX:
644       secstrcode[pos] = 'H';
645       break;
646     case SHEET:
647       secstrcode[pos] = 'E';
648       break;
649     default:
650       secstrcode[pos] = 0;
651     }
652   }
653
654   private char getSecondayStructure(STR proteinStructureSubType)
655   {
656     switch (proteinStructureSubType)
657     {
658     case HELIX310:
659       return '3';
660     case HELIX:
661     case HELIXALPHA:
662       return 'H';
663     case HELIXPI:
664       return 'P';
665     case SHEET:
666       return 'E';
667     default:
668       return 0;
669     }
670   }
671
672   /**
673    * Convert any non-standard peptide codes to their standard code table
674    * equivalent. (Initial version only does Selenomethionine MSE->MET.)
675    * 
676    * @param threeLetterCode
677    * @param seq
678    * @param pos
679    */
680   protected void replaceNonCanonicalResidue(String threeLetterCode,
681           char[] seq, int pos)
682   {
683     String canonical = ResidueProperties
684             .getCanonicalAminoAcid(threeLetterCode);
685     if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
686     {
687       seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
688     }
689   }
690
691   /**
692    * Not implemented - returns null
693    */
694   @Override
695   public String print()
696   {
697     return null;
698   }
699
700   /**
701    * Not implemented
702    */
703   @Override
704   public void setCallbackFunction(String callbackType,
705           String callbackFunction)
706   {
707   }
708
709   @Override
710   public void notifyCallback(CBK cbType, Object[] data)
711   {
712     String strInfo = (data == null || data[1] == null ? null : data[1]
713             .toString());
714     switch (cbType)
715     {
716     case ECHO:
717       sendConsoleEcho(strInfo);
718       break;
719     case SCRIPT:
720       notifyScriptTermination((String) data[2],
721               ((Integer) data[3]).intValue());
722       break;
723     case MEASURE:
724       String mystatus = (String) data[3];
725       if (mystatus.indexOf("Picked") >= 0
726               || mystatus.indexOf("Sequence") >= 0)
727       {
728         // Picking mode
729         sendConsoleMessage(strInfo);
730       }
731       else if (mystatus.indexOf("Completed") >= 0)
732       {
733         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
734                 strInfo.length() - 1));
735       }
736       break;
737     case MESSAGE:
738       sendConsoleMessage(data == null ? null : strInfo);
739       break;
740     case PICK:
741       sendConsoleMessage(strInfo);
742       break;
743     default:
744       break;
745     }
746   }
747
748   String lastConsoleEcho = "";
749
750   private void sendConsoleEcho(String string)
751   {
752     lastConsoleEcho += string;
753     lastConsoleEcho += "\n";
754   }
755
756   String lastConsoleMessage = "";
757
758   private void sendConsoleMessage(String string)
759   {
760     lastConsoleMessage += string;
761     lastConsoleMessage += "\n";
762   }
763
764   int lastScriptTermination = -1;
765
766   String lastScriptMessage = "";
767
768   private void notifyScriptTermination(String string, int intValue)
769   {
770     lastScriptMessage += string;
771     lastScriptMessage += "\n";
772     lastScriptTermination = intValue;
773   }
774
775   @Override
776   public boolean notifyEnabled(CBK callbackPick)
777   {
778     switch (callbackPick)
779     {
780     case MESSAGE:
781     case SCRIPT:
782     case ECHO:
783     case LOADSTRUCT:
784     case ERROR:
785       return true;
786     default:
787       return false;
788     }
789   }
790
791   /**
792    * Not implemented - returns null
793    */
794   @Override
795   public String eval(String strEval)
796   {
797     return null;
798   }
799
800   /**
801    * Not implemented - returns null
802    */
803   @Override
804   public float[][] functionXY(String functionName, int x, int y)
805   {
806     return null;
807   }
808
809   /**
810    * Not implemented - returns null
811    */
812   @Override
813   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
814   {
815     return null;
816   }
817
818   /**
819    * Not implemented - returns null
820    */
821   @Override
822   public String createImage(String fileName, String imageType,
823           Object text_or_bytes, int quality)
824   {
825     return null;
826   }
827
828   /**
829    * Not implemented - returns null
830    */
831   @Override
832   public Map<String, Object> getRegistryInfo()
833   {
834     return null;
835   }
836
837   /**
838    * Not implemented
839    */
840   @Override
841   public void showUrl(String url)
842   {
843   }
844
845   /**
846    * Not implemented - returns null
847    */
848   @Override
849   public Dimension resizeInnerPanel(String data)
850   {
851     return null;
852   }
853
854   @Override
855   public Map<String, Object> getJSpecViewProperty(String arg0)
856   {
857     return null;
858   }
859
860   public boolean isPredictSecondaryStructure()
861   {
862     return predictSecondaryStructure;
863   }
864
865   public void setPredictSecondaryStructure(boolean predictSecondaryStructure)
866   {
867     this.predictSecondaryStructure = predictSecondaryStructure;
868   }
869
870 }