file format enum wip changes
[jalview.git] / src / jalview / gui / Jalview2XML.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.gui;
22
23 import jalview.api.FeatureColourI;
24 import jalview.api.ViewStyleI;
25 import jalview.api.structures.JalviewStructureDisplayI;
26 import jalview.bin.Cache;
27 import jalview.datamodel.AlignedCodonFrame;
28 import jalview.datamodel.Alignment;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.PDBEntry;
32 import jalview.datamodel.RnaViewerModel;
33 import jalview.datamodel.SequenceGroup;
34 import jalview.datamodel.SequenceI;
35 import jalview.datamodel.StructureViewerModel;
36 import jalview.datamodel.StructureViewerModel.StructureData;
37 import jalview.ext.varna.RnaModel;
38 import jalview.gui.StructureViewer.ViewerType;
39 import jalview.io.DataSourceType;
40 import jalview.schemabinding.version2.AlcodMap;
41 import jalview.schemabinding.version2.AlcodonFrame;
42 import jalview.schemabinding.version2.Annotation;
43 import jalview.schemabinding.version2.AnnotationColours;
44 import jalview.schemabinding.version2.AnnotationElement;
45 import jalview.schemabinding.version2.CalcIdParam;
46 import jalview.schemabinding.version2.DBRef;
47 import jalview.schemabinding.version2.Features;
48 import jalview.schemabinding.version2.Group;
49 import jalview.schemabinding.version2.HiddenColumns;
50 import jalview.schemabinding.version2.JGroup;
51 import jalview.schemabinding.version2.JSeq;
52 import jalview.schemabinding.version2.JalviewModel;
53 import jalview.schemabinding.version2.JalviewModelSequence;
54 import jalview.schemabinding.version2.MapListFrom;
55 import jalview.schemabinding.version2.MapListTo;
56 import jalview.schemabinding.version2.Mapping;
57 import jalview.schemabinding.version2.MappingChoice;
58 import jalview.schemabinding.version2.OtherData;
59 import jalview.schemabinding.version2.PdbentryItem;
60 import jalview.schemabinding.version2.Pdbids;
61 import jalview.schemabinding.version2.Property;
62 import jalview.schemabinding.version2.RnaViewer;
63 import jalview.schemabinding.version2.SecondaryStructure;
64 import jalview.schemabinding.version2.Sequence;
65 import jalview.schemabinding.version2.SequenceSet;
66 import jalview.schemabinding.version2.SequenceSetProperties;
67 import jalview.schemabinding.version2.Setting;
68 import jalview.schemabinding.version2.StructureState;
69 import jalview.schemabinding.version2.ThresholdLine;
70 import jalview.schemabinding.version2.Tree;
71 import jalview.schemabinding.version2.UserColours;
72 import jalview.schemabinding.version2.Viewport;
73 import jalview.schemes.AnnotationColourGradient;
74 import jalview.schemes.ColourSchemeI;
75 import jalview.schemes.ColourSchemeProperty;
76 import jalview.schemes.FeatureColour;
77 import jalview.schemes.ResidueColourScheme;
78 import jalview.schemes.ResidueProperties;
79 import jalview.schemes.UserColourScheme;
80 import jalview.structure.StructureSelectionManager;
81 import jalview.structures.models.AAStructureBindingModel;
82 import jalview.util.MessageManager;
83 import jalview.util.Platform;
84 import jalview.util.StringUtils;
85 import jalview.util.jarInputStreamProvider;
86 import jalview.viewmodel.AlignmentViewport;
87 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
88 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
89 import jalview.ws.jws2.Jws2Discoverer;
90 import jalview.ws.jws2.dm.AAConSettings;
91 import jalview.ws.jws2.jabaws2.Jws2Instance;
92 import jalview.ws.params.ArgumentI;
93 import jalview.ws.params.AutoCalcSetting;
94 import jalview.ws.params.WsParamSetI;
95
96 import java.awt.Color;
97 import java.awt.Rectangle;
98 import java.io.BufferedReader;
99 import java.io.DataInputStream;
100 import java.io.DataOutputStream;
101 import java.io.File;
102 import java.io.FileInputStream;
103 import java.io.FileOutputStream;
104 import java.io.IOException;
105 import java.io.InputStreamReader;
106 import java.io.OutputStreamWriter;
107 import java.io.PrintWriter;
108 import java.lang.reflect.InvocationTargetException;
109 import java.net.MalformedURLException;
110 import java.net.URL;
111 import java.util.ArrayList;
112 import java.util.Enumeration;
113 import java.util.HashMap;
114 import java.util.HashSet;
115 import java.util.Hashtable;
116 import java.util.IdentityHashMap;
117 import java.util.Iterator;
118 import java.util.LinkedHashMap;
119 import java.util.List;
120 import java.util.Map;
121 import java.util.Map.Entry;
122 import java.util.Set;
123 import java.util.Vector;
124 import java.util.jar.JarEntry;
125 import java.util.jar.JarInputStream;
126 import java.util.jar.JarOutputStream;
127
128 import javax.swing.JInternalFrame;
129 import javax.swing.JOptionPane;
130 import javax.swing.SwingUtilities;
131
132 import org.exolab.castor.xml.Marshaller;
133 import org.exolab.castor.xml.Unmarshaller;
134
135 /**
136  * Write out the current jalview desktop state as a Jalview XML stream.
137  * 
138  * Note: the vamsas objects referred to here are primitive versions of the
139  * VAMSAS project schema elements - they are not the same and most likely never
140  * will be :)
141  * 
142  * @author $author$
143  * @version $Revision: 1.134 $
144  */
145 public class Jalview2XML
146 {
147   private static final String VIEWER_PREFIX = "viewer_";
148
149   private static final String RNA_PREFIX = "rna_";
150
151   private static final String UTF_8 = "UTF-8";
152
153   // use this with nextCounter() to make unique names for entities
154   private int counter = 0;
155
156   /*
157    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
158    * of sequence objects are created.
159    */
160   IdentityHashMap<SequenceI, String> seqsToIds = null;
161
162   /**
163    * jalview XML Sequence ID to jalview sequence object reference (both dataset
164    * and alignment sequences. Populated as XML reps of sequence objects are
165    * created.)
166    */
167   Map<String, SequenceI> seqRefIds = null;
168
169   Vector<Object[]> frefedSequence = null;
170
171   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
172
173   /*
174    * Map of reconstructed AlignFrame objects that appear to have come from
175    * SplitFrame objects (have a dna/protein complement view).
176    */
177   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<Viewport, AlignFrame>();
178
179   /*
180    * Map from displayed rna structure models to their saved session state jar
181    * entry names
182    */
183   private Map<RnaModel, String> rnaSessions = new HashMap<RnaModel, String>();
184
185   /**
186    * create/return unique hash string for sq
187    * 
188    * @param sq
189    * @return new or existing unique string for sq
190    */
191   String seqHash(SequenceI sq)
192   {
193     if (seqsToIds == null)
194     {
195       initSeqRefs();
196     }
197     if (seqsToIds.containsKey(sq))
198     {
199       return seqsToIds.get(sq);
200     }
201     else
202     {
203       // create sequential key
204       String key = "sq" + (seqsToIds.size() + 1);
205       key = makeHashCode(sq, key); // check we don't have an external reference
206       // for it already.
207       seqsToIds.put(sq, key);
208       return key;
209     }
210   }
211
212   void clearSeqRefs()
213   {
214     if (_cleartables)
215     {
216       if (seqRefIds != null)
217       {
218         seqRefIds.clear();
219       }
220       if (seqsToIds != null)
221       {
222         seqsToIds.clear();
223       }
224       // seqRefIds = null;
225       // seqsToIds = null;
226     }
227     else
228     {
229       // do nothing
230       warn("clearSeqRefs called when _cleartables was not set. Doing nothing.");
231       // seqRefIds = new Hashtable();
232       // seqsToIds = new IdentityHashMap();
233     }
234   }
235
236   void initSeqRefs()
237   {
238     if (seqsToIds == null)
239     {
240       seqsToIds = new IdentityHashMap<SequenceI, String>();
241     }
242     if (seqRefIds == null)
243     {
244       seqRefIds = new HashMap<String, SequenceI>();
245     }
246   }
247
248   public Jalview2XML()
249   {
250   }
251
252   public Jalview2XML(boolean raiseGUI)
253   {
254     this.raiseGUI = raiseGUI;
255   }
256
257   public void resolveFrefedSequences()
258   {
259     if (frefedSequence.size() > 0)
260     {
261       int r = 0, rSize = frefedSequence.size();
262       while (r < rSize)
263       {
264         Object[] ref = frefedSequence.elementAt(r);
265         if (ref != null)
266         {
267           String sref = (String) ref[0];
268           if (seqRefIds.containsKey(sref))
269           {
270             if (ref[1] instanceof jalview.datamodel.Mapping)
271             {
272               SequenceI seq = seqRefIds.get(sref);
273               while (seq.getDatasetSequence() != null)
274               {
275                 seq = seq.getDatasetSequence();
276               }
277               ((jalview.datamodel.Mapping) ref[1]).setTo(seq);
278             }
279             else
280             {
281               if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
282               {
283                 SequenceI seq = seqRefIds.get(sref);
284                 while (seq.getDatasetSequence() != null)
285                 {
286                   seq = seq.getDatasetSequence();
287                 }
288                 if (ref[2] != null
289                         && ref[2] instanceof jalview.datamodel.Mapping)
290                 {
291                   jalview.datamodel.Mapping mp = (jalview.datamodel.Mapping) ref[2];
292                   ((jalview.datamodel.AlignedCodonFrame) ref[1]).addMap(
293                           seq, mp.getTo(), mp.getMap());
294                 }
295                 else
296                 {
297                   System.err
298                           .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for AlcodonFrames involving "
299                                   + ref[2].getClass() + " type objects.");
300                 }
301               }
302               else
303               {
304                 System.err
305                         .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for "
306                                 + ref[1].getClass() + " type objects.");
307               }
308             }
309             frefedSequence.remove(r);
310             rSize--;
311           }
312           else
313           {
314             System.err
315                     .println("IMPLEMENTATION WARNING: Unresolved forward reference for hash string "
316                             + ref[0]
317                             + " with objecttype "
318                             + ref[1].getClass());
319             r++;
320           }
321         }
322         else
323         {
324           // empty reference
325           frefedSequence.remove(r);
326           rSize--;
327         }
328       }
329     }
330   }
331
332   /**
333    * This maintains a map of viewports, the key being the seqSetId. Important to
334    * set historyItem and redoList for multiple views
335    */
336   Map<String, AlignViewport> viewportsAdded = new HashMap<String, AlignViewport>();
337
338   Map<String, AlignmentAnnotation> annotationIds = new HashMap<String, AlignmentAnnotation>();
339
340   String uniqueSetSuffix = "";
341
342   /**
343    * List of pdbfiles added to Jar
344    */
345   List<String> pdbfiles = null;
346
347   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
348   public void saveState(File statefile)
349   {
350     FileOutputStream fos = null;
351     try
352     {
353       fos = new FileOutputStream(statefile);
354       JarOutputStream jout = new JarOutputStream(fos);
355       saveState(jout);
356
357     } catch (Exception e)
358     {
359       // TODO: inform user of the problem - they need to know if their data was
360       // not saved !
361       if (errorMessage == null)
362       {
363         errorMessage = "Couldn't write Jalview Archive to output file '"
364                 + statefile + "' - See console error log for details";
365       }
366       else
367       {
368         errorMessage += "(output file was '" + statefile + "')";
369       }
370       e.printStackTrace();
371     } finally
372     {
373       if (fos != null)
374       {
375         try
376         {
377           fos.close();
378         } catch (IOException e)
379         {
380           // ignore
381         }
382       }
383     }
384     reportErrors();
385   }
386
387   /**
388    * Writes a jalview project archive to the given Jar output stream.
389    * 
390    * @param jout
391    */
392   public void saveState(JarOutputStream jout)
393   {
394     AlignFrame[] frames = Desktop.getAlignFrames();
395
396     if (frames == null)
397     {
398       return;
399     }
400
401     Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
402
403     /*
404      * ensure cached data is clear before starting
405      */
406     // todo tidy up seqRefIds, seqsToIds initialisation / reset
407     rnaSessions.clear();
408     splitFrameCandidates.clear();
409
410     try
411     {
412
413       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
414       // //////////////////////////////////////////////////
415
416       List<String> shortNames = new ArrayList<String>();
417       List<String> viewIds = new ArrayList<String>();
418
419       // REVERSE ORDER
420       for (int i = frames.length - 1; i > -1; i--)
421       {
422         AlignFrame af = frames[i];
423         // skip ?
424         if (skipList != null
425                 && skipList
426                         .containsKey(af.getViewport().getSequenceSetId()))
427         {
428           continue;
429         }
430
431         String shortName = makeFilename(af, shortNames);
432
433         int ap, apSize = af.alignPanels.size();
434
435         for (ap = 0; ap < apSize; ap++)
436         {
437           AlignmentPanel apanel = af.alignPanels.get(ap);
438           String fileName = apSize == 1 ? shortName : ap + shortName;
439           if (!fileName.endsWith(".xml"))
440           {
441             fileName = fileName + ".xml";
442           }
443
444           saveState(apanel, fileName, jout, viewIds);
445
446           String dssid = getDatasetIdRef(af.getViewport().getAlignment()
447                   .getDataset());
448           if (!dsses.containsKey(dssid))
449           {
450             dsses.put(dssid, af);
451           }
452         }
453       }
454
455       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
456               jout);
457
458       try
459       {
460         jout.flush();
461       } catch (Exception foo)
462       {
463       }
464       ;
465       jout.close();
466     } catch (Exception ex)
467     {
468       // TODO: inform user of the problem - they need to know if their data was
469       // not saved !
470       if (errorMessage == null)
471       {
472         errorMessage = "Couldn't write Jalview Archive - see error output for details";
473       }
474       ex.printStackTrace();
475     }
476   }
477
478   /**
479    * Generates a distinct file name, based on the title of the AlignFrame, by
480    * appending _n for increasing n until an unused name is generated. The new
481    * name (without its extension) is added to the list.
482    * 
483    * @param af
484    * @param namesUsed
485    * @return the generated name, with .xml extension
486    */
487   protected String makeFilename(AlignFrame af, List<String> namesUsed)
488   {
489     String shortName = af.getTitle();
490
491     if (shortName.indexOf(File.separatorChar) > -1)
492     {
493       shortName = shortName.substring(shortName
494               .lastIndexOf(File.separatorChar) + 1);
495     }
496
497     int count = 1;
498
499     while (namesUsed.contains(shortName))
500     {
501       if (shortName.endsWith("_" + (count - 1)))
502       {
503         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
504       }
505
506       shortName = shortName.concat("_" + count);
507       count++;
508     }
509
510     namesUsed.add(shortName);
511
512     if (!shortName.endsWith(".xml"))
513     {
514       shortName = shortName + ".xml";
515     }
516     return shortName;
517   }
518
519   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
520   public boolean saveAlignment(AlignFrame af, String jarFile,
521           String fileName)
522   {
523     try
524     {
525       int ap = 0;
526       int apSize = af.alignPanels.size();
527       FileOutputStream fos = new FileOutputStream(jarFile);
528       JarOutputStream jout = new JarOutputStream(fos);
529       Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
530       List<String> viewIds = new ArrayList<String>();
531
532       for (AlignmentPanel apanel : af.alignPanels)
533       {
534         String jfileName = apSize == 1 ? fileName : fileName + ap;
535         ap++;
536         if (!jfileName.endsWith(".xml"))
537         {
538           jfileName = jfileName + ".xml";
539         }
540         saveState(apanel, jfileName, jout, viewIds);
541         String dssid = getDatasetIdRef(af.getViewport().getAlignment()
542                 .getDataset());
543         if (!dsses.containsKey(dssid))
544         {
545           dsses.put(dssid, af);
546         }
547       }
548       writeDatasetFor(dsses, fileName, jout);
549       try
550       {
551         jout.flush();
552       } catch (Exception foo)
553       {
554       }
555       ;
556       jout.close();
557       return true;
558     } catch (Exception ex)
559     {
560       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
561       ex.printStackTrace();
562       return false;
563     }
564   }
565
566   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
567           String fileName, JarOutputStream jout)
568   {
569
570     for (String dssids : dsses.keySet())
571     {
572       AlignFrame _af = dsses.get(dssids);
573       String jfileName = fileName + " Dataset for " + _af.getTitle();
574       if (!jfileName.endsWith(".xml"))
575       {
576         jfileName = jfileName + ".xml";
577       }
578       saveState(_af.alignPanel, jfileName, true, jout, null);
579     }
580   }
581
582   /**
583    * create a JalviewModel from an alignment view and marshall it to a
584    * JarOutputStream
585    * 
586    * @param ap
587    *          panel to create jalview model for
588    * @param fileName
589    *          name of alignment panel written to output stream
590    * @param jout
591    *          jar output stream
592    * @param viewIds
593    * @param out
594    *          jar entry name
595    */
596   public JalviewModel saveState(AlignmentPanel ap, String fileName,
597           JarOutputStream jout, List<String> viewIds)
598   {
599     return saveState(ap, fileName, false, jout, viewIds);
600   }
601
602   /**
603    * create a JalviewModel from an alignment view and marshall it to a
604    * JarOutputStream
605    * 
606    * @param ap
607    *          panel to create jalview model for
608    * @param fileName
609    *          name of alignment panel written to output stream
610    * @param storeDS
611    *          when true, only write the dataset for the alignment, not the data
612    *          associated with the view.
613    * @param jout
614    *          jar output stream
615    * @param out
616    *          jar entry name
617    */
618   public JalviewModel saveState(AlignmentPanel ap, String fileName,
619           boolean storeDS, JarOutputStream jout, List<String> viewIds)
620   {
621     if (viewIds == null)
622     {
623       viewIds = new ArrayList<String>();
624     }
625
626     initSeqRefs();
627
628     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
629
630     AlignViewport av = ap.av;
631
632     JalviewModel object = new JalviewModel();
633     object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
634
635     object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
636     object.setVersion(jalview.bin.Cache.getDefault("VERSION",
637             "Development Build"));
638
639     /**
640      * rjal is full height alignment, jal is actual alignment with full metadata
641      * but excludes hidden sequences.
642      */
643     jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
644
645     if (av.hasHiddenRows())
646     {
647       rjal = jal.getHiddenSequences().getFullAlignment();
648     }
649
650     SequenceSet vamsasSet = new SequenceSet();
651     Sequence vamsasSeq;
652     JalviewModelSequence jms = new JalviewModelSequence();
653
654     vamsasSet.setGapChar(jal.getGapCharacter() + "");
655
656     if (jal.getDataset() != null)
657     {
658       // dataset id is the dataset's hashcode
659       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
660       if (storeDS)
661       {
662         // switch jal and the dataset
663         jal = jal.getDataset();
664         rjal = jal;
665       }
666     }
667     if (jal.getProperties() != null)
668     {
669       Enumeration en = jal.getProperties().keys();
670       while (en.hasMoreElements())
671       {
672         String key = en.nextElement().toString();
673         SequenceSetProperties ssp = new SequenceSetProperties();
674         ssp.setKey(key);
675         ssp.setValue(jal.getProperties().get(key).toString());
676         vamsasSet.addSequenceSetProperties(ssp);
677       }
678     }
679
680     JSeq jseq;
681     Set<String> calcIdSet = new HashSet<String>();
682
683     // SAVE SEQUENCES
684     for (final SequenceI jds : rjal.getSequences())
685     {
686       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
687               : jds.getDatasetSequence();
688       String id = seqHash(jds);
689
690       if (seqRefIds.get(id) != null)
691       {
692         // This happens for two reasons: 1. multiple views are being serialised.
693         // 2. the hashCode has collided with another sequence's code. This DOES
694         // HAPPEN! (PF00072.15.stk does this)
695         // JBPNote: Uncomment to debug writing out of files that do not read
696         // back in due to ArrayOutOfBoundExceptions.
697         // System.err.println("vamsasSeq backref: "+id+"");
698         // System.err.println(jds.getName()+"
699         // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
700         // System.err.println("Hashcode: "+seqHash(jds));
701         // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
702         // System.err.println(rsq.getName()+"
703         // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
704         // System.err.println("Hashcode: "+seqHash(rsq));
705       }
706       else
707       {
708         vamsasSeq = createVamsasSequence(id, jds);
709         vamsasSet.addSequence(vamsasSeq);
710         seqRefIds.put(id, jds);
711       }
712
713       jseq = new JSeq();
714       jseq.setStart(jds.getStart());
715       jseq.setEnd(jds.getEnd());
716       jseq.setColour(av.getSequenceColour(jds).getRGB());
717
718       jseq.setId(id); // jseq id should be a string not a number
719       if (!storeDS)
720       {
721         // Store any sequences this sequence represents
722         if (av.hasHiddenRows())
723         {
724           // use rjal, contains the full height alignment
725           jseq.setHidden(av.getAlignment().getHiddenSequences()
726                   .isHidden(jds));
727
728           if (av.isHiddenRepSequence(jds))
729           {
730             jalview.datamodel.SequenceI[] reps = av
731                     .getRepresentedSequences(jds)
732                     .getSequencesInOrder(rjal);
733
734             for (int h = 0; h < reps.length; h++)
735             {
736               if (reps[h] != jds)
737               {
738                 jseq.addHiddenSequences(rjal.findIndex(reps[h]));
739               }
740             }
741           }
742         }
743         // mark sequence as reference - if it is the reference for this view
744         if (jal.hasSeqrep())
745         {
746           jseq.setViewreference(jds == jal.getSeqrep());
747         }
748       }
749
750       // TODO: omit sequence features from each alignment view's XML dump if we
751       // are storing dataset
752       if (jds.getSequenceFeatures() != null)
753       {
754         jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
755         int index = 0;
756         while (index < sf.length)
757         {
758           Features features = new Features();
759
760           features.setBegin(sf[index].getBegin());
761           features.setEnd(sf[index].getEnd());
762           features.setDescription(sf[index].getDescription());
763           features.setType(sf[index].getType());
764           features.setFeatureGroup(sf[index].getFeatureGroup());
765           features.setScore(sf[index].getScore());
766           if (sf[index].links != null)
767           {
768             for (int l = 0; l < sf[index].links.size(); l++)
769             {
770               OtherData keyValue = new OtherData();
771               keyValue.setKey("LINK_" + l);
772               keyValue.setValue(sf[index].links.elementAt(l).toString());
773               features.addOtherData(keyValue);
774             }
775           }
776           if (sf[index].otherDetails != null)
777           {
778             String key;
779             Iterator<String> keys = sf[index].otherDetails.keySet()
780                     .iterator();
781             while (keys.hasNext())
782             {
783               key = keys.next();
784               OtherData keyValue = new OtherData();
785               keyValue.setKey(key);
786               keyValue.setValue(sf[index].otherDetails.get(key).toString());
787               features.addOtherData(keyValue);
788             }
789           }
790
791           jseq.addFeatures(features);
792           index++;
793         }
794       }
795
796       if (jdatasq.getAllPDBEntries() != null)
797       {
798         Enumeration en = jdatasq.getAllPDBEntries().elements();
799         while (en.hasMoreElements())
800         {
801           Pdbids pdb = new Pdbids();
802           jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en
803                   .nextElement();
804
805           String pdbId = entry.getId();
806           pdb.setId(pdbId);
807           pdb.setType(entry.getType());
808
809           /*
810            * Store any structure views associated with this sequence. This
811            * section copes with duplicate entries in the project, so a dataset
812            * only view *should* be coped with sensibly.
813            */
814           // This must have been loaded, is it still visible?
815           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
816           String matchedFile = null;
817           for (int f = frames.length - 1; f > -1; f--)
818           {
819             if (frames[f] instanceof StructureViewerBase)
820             {
821               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
822               matchedFile = saveStructureState(ap, jds, pdb, entry,
823                       viewIds, matchedFile, viewFrame);
824               /*
825                * Only store each structure viewer's state once in the project
826                * jar. First time through only (storeDS==false)
827                */
828               String viewId = viewFrame.getViewId();
829               if (!storeDS && !viewIds.contains(viewId))
830               {
831                 viewIds.add(viewId);
832                 try
833                 {
834                   String viewerState = viewFrame.getStateInfo();
835                   writeJarEntry(jout, getViewerJarEntryName(viewId),
836                           viewerState.getBytes());
837                 } catch (IOException e)
838                 {
839                   System.err.println("Error saving viewer state: "
840                           + e.getMessage());
841                 }
842               }
843             }
844           }
845
846           if (matchedFile != null || entry.getFile() != null)
847           {
848             if (entry.getFile() != null)
849             {
850               // use entry's file
851               matchedFile = entry.getFile();
852             }
853             pdb.setFile(matchedFile); // entry.getFile());
854             if (pdbfiles == null)
855             {
856               pdbfiles = new ArrayList<String>();
857             }
858
859             if (!pdbfiles.contains(pdbId))
860             {
861               pdbfiles.add(pdbId);
862               copyFileToJar(jout, matchedFile, pdbId);
863             }
864           }
865
866           if (entry.getProperty() != null && !entry.getProperty().isEmpty())
867           {
868             PdbentryItem item = new PdbentryItem();
869             Hashtable<String, String> properties = entry.getProperty();
870             Enumeration<String> en2 = properties.keys();
871             while (en2.hasMoreElements())
872             {
873               Property prop = new Property();
874               String key = en2.nextElement();
875               prop.setName(key);
876               prop.setValue(properties.get(key));
877               item.addProperty(prop);
878             }
879             pdb.addPdbentryItem(item);
880           }
881
882           jseq.addPdbids(pdb);
883         }
884       }
885
886       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
887
888       jms.addJSeq(jseq);
889     }
890
891     if (!storeDS && av.hasHiddenRows())
892     {
893       jal = av.getAlignment();
894     }
895     // SAVE MAPPINGS
896     if (jal.getCodonFrames() != null)
897     {
898       List<AlignedCodonFrame> jac = jal.getCodonFrames();
899       for (AlignedCodonFrame acf : jac)
900       {
901         AlcodonFrame alc = new AlcodonFrame();
902         vamsasSet.addAlcodonFrame(alc);
903         if (acf.getProtMappings() != null
904                 && acf.getProtMappings().length > 0)
905         {
906           SequenceI[] dnas = acf.getdnaSeqs();
907           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
908           for (int m = 0; m < pmaps.length; m++)
909           {
910             AlcodMap alcmap = new AlcodMap();
911             alcmap.setDnasq(seqHash(dnas[m]));
912             alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
913                     false));
914             alc.addAlcodMap(alcmap);
915           }
916         }
917         // TODO: delete this ? dead code from 2.8.3->2.9 ?
918         // {
919         // AlcodonFrame alc = new AlcodonFrame();
920         // vamsasSet.addAlcodonFrame(alc);
921         // for (int p = 0; p < acf.aaWidth; p++)
922         // {
923         // Alcodon cmap = new Alcodon();
924         // if (acf.codons[p] != null)
925         // {
926         // // Null codons indicate a gapped column in the translated peptide
927         // // alignment.
928         // cmap.setPos1(acf.codons[p][0]);
929         // cmap.setPos2(acf.codons[p][1]);
930         // cmap.setPos3(acf.codons[p][2]);
931         // }
932         // alc.addAlcodon(cmap);
933         // }
934         // if (acf.getProtMappings() != null
935         // && acf.getProtMappings().length > 0)
936         // {
937         // SequenceI[] dnas = acf.getdnaSeqs();
938         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
939         // for (int m = 0; m < pmaps.length; m++)
940         // {
941         // AlcodMap alcmap = new AlcodMap();
942         // alcmap.setDnasq(seqHash(dnas[m]));
943         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
944         // false));
945         // alc.addAlcodMap(alcmap);
946         // }
947         // }
948       }
949     }
950
951     // SAVE TREES
952     // /////////////////////////////////
953     if (!storeDS && av.currentTree != null)
954     {
955       // FIND ANY ASSOCIATED TREES
956       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
957       if (Desktop.desktop != null)
958       {
959         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
960
961         for (int t = 0; t < frames.length; t++)
962         {
963           if (frames[t] instanceof TreePanel)
964           {
965             TreePanel tp = (TreePanel) frames[t];
966
967             if (tp.treeCanvas.av.getAlignment() == jal)
968             {
969               Tree tree = new Tree();
970               tree.setTitle(tp.getTitle());
971               tree.setCurrentTree((av.currentTree == tp.getTree()));
972               tree.setNewick(tp.getTree().toString());
973               tree.setThreshold(tp.treeCanvas.threshold);
974
975               tree.setFitToWindow(tp.fitToWindow.getState());
976               tree.setFontName(tp.getTreeFont().getName());
977               tree.setFontSize(tp.getTreeFont().getSize());
978               tree.setFontStyle(tp.getTreeFont().getStyle());
979               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
980
981               tree.setShowBootstrap(tp.bootstrapMenu.getState());
982               tree.setShowDistances(tp.distanceMenu.getState());
983
984               tree.setHeight(tp.getHeight());
985               tree.setWidth(tp.getWidth());
986               tree.setXpos(tp.getX());
987               tree.setYpos(tp.getY());
988               tree.setId(makeHashCode(tp, null));
989               jms.addTree(tree);
990             }
991           }
992         }
993       }
994     }
995
996     // SAVE ANNOTATIONS
997     /**
998      * store forward refs from an annotationRow to any groups
999      */
1000     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<SequenceGroup, String>();
1001     if (storeDS)
1002     {
1003       for (SequenceI sq : jal.getSequences())
1004       {
1005         // Store annotation on dataset sequences only
1006         AlignmentAnnotation[] aa = sq.getAnnotation();
1007         if (aa != null && aa.length > 0)
1008         {
1009           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1010                   vamsasSet);
1011         }
1012       }
1013     }
1014     else
1015     {
1016       if (jal.getAlignmentAnnotation() != null)
1017       {
1018         // Store the annotation shown on the alignment.
1019         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
1020         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1021                 vamsasSet);
1022       }
1023     }
1024     // SAVE GROUPS
1025     if (jal.getGroups() != null)
1026     {
1027       JGroup[] groups = new JGroup[jal.getGroups().size()];
1028       int i = -1;
1029       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
1030       {
1031         JGroup jGroup = new JGroup();
1032         groups[++i] = jGroup;
1033
1034         jGroup.setStart(sg.getStartRes());
1035         jGroup.setEnd(sg.getEndRes());
1036         jGroup.setName(sg.getName());
1037         if (groupRefs.containsKey(sg))
1038         {
1039           // group has references so set its ID field
1040           jGroup.setId(groupRefs.get(sg));
1041         }
1042         if (sg.cs != null)
1043         {
1044           if (sg.cs.conservationApplied())
1045           {
1046             jGroup.setConsThreshold(sg.cs.getConservationInc());
1047
1048             if (sg.cs instanceof jalview.schemes.UserColourScheme)
1049             {
1050               jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1051             }
1052             else
1053             {
1054               jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1055             }
1056           }
1057           else if (sg.cs instanceof jalview.schemes.AnnotationColourGradient)
1058           {
1059             jGroup.setColour("AnnotationColourGradient");
1060             jGroup.setAnnotationColours(constructAnnotationColours(
1061                     (jalview.schemes.AnnotationColourGradient) sg.cs,
1062                     userColours, jms));
1063           }
1064           else if (sg.cs instanceof jalview.schemes.UserColourScheme)
1065           {
1066             jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1067           }
1068           else
1069           {
1070             jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1071           }
1072
1073           jGroup.setPidThreshold(sg.cs.getThreshold());
1074         }
1075
1076         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1077         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1078         jGroup.setDisplayText(sg.getDisplayText());
1079         jGroup.setColourText(sg.getColourText());
1080         jGroup.setTextCol1(sg.textColour.getRGB());
1081         jGroup.setTextCol2(sg.textColour2.getRGB());
1082         jGroup.setTextColThreshold(sg.thresholdTextColour);
1083         jGroup.setShowUnconserved(sg.getShowNonconserved());
1084         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1085         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1086         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1087         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1088         for (SequenceI seq : sg.getSequences())
1089         {
1090           jGroup.addSeq(seqHash(seq));
1091         }
1092       }
1093
1094       jms.setJGroup(groups);
1095     }
1096     if (!storeDS)
1097     {
1098       // /////////SAVE VIEWPORT
1099       Viewport view = new Viewport();
1100       view.setTitle(ap.alignFrame.getTitle());
1101       view.setSequenceSetId(makeHashCode(av.getSequenceSetId(),
1102               av.getSequenceSetId()));
1103       view.setId(av.getViewId());
1104       if (av.getCodingComplement() != null)
1105       {
1106         view.setComplementId(av.getCodingComplement().getViewId());
1107       }
1108       view.setViewName(av.viewName);
1109       view.setGatheredViews(av.isGatherViewsHere());
1110
1111       Rectangle size = ap.av.getExplodedGeometry();
1112       Rectangle position = size;
1113       if (size == null)
1114       {
1115         size = ap.alignFrame.getBounds();
1116         if (av.getCodingComplement() != null)
1117         {
1118           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
1119                   .getBounds();
1120         }
1121         else
1122         {
1123           position = size;
1124         }
1125       }
1126       view.setXpos(position.x);
1127       view.setYpos(position.y);
1128
1129       view.setWidth(size.width);
1130       view.setHeight(size.height);
1131
1132       view.setStartRes(av.startRes);
1133       view.setStartSeq(av.startSeq);
1134
1135       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1136       {
1137         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1138                 userColours, jms));
1139       }
1140       else if (av.getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
1141       {
1142         AnnotationColours ac = constructAnnotationColours(
1143                 (jalview.schemes.AnnotationColourGradient) av
1144                         .getGlobalColourScheme(),
1145                 userColours, jms);
1146
1147         view.setAnnotationColours(ac);
1148         view.setBgColour("AnnotationColourGradient");
1149       }
1150       else
1151       {
1152         view.setBgColour(ColourSchemeProperty.getColourName(av
1153                 .getGlobalColourScheme()));
1154       }
1155
1156       ColourSchemeI cs = av.getGlobalColourScheme();
1157
1158       if (cs != null)
1159       {
1160         if (cs.conservationApplied())
1161         {
1162           view.setConsThreshold(cs.getConservationInc());
1163           if (cs instanceof jalview.schemes.UserColourScheme)
1164           {
1165             view.setBgColour(setUserColourScheme(cs, userColours, jms));
1166           }
1167         }
1168
1169         if (cs instanceof ResidueColourScheme)
1170         {
1171           view.setPidThreshold(cs.getThreshold());
1172         }
1173       }
1174
1175       view.setConservationSelected(av.getConservationSelected());
1176       view.setPidSelected(av.getAbovePIDThreshold());
1177       view.setFontName(av.font.getName());
1178       view.setFontSize(av.font.getSize());
1179       view.setFontStyle(av.font.getStyle());
1180       view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
1181       view.setRenderGaps(av.isRenderGaps());
1182       view.setShowAnnotation(av.isShowAnnotation());
1183       view.setShowBoxes(av.getShowBoxes());
1184       view.setShowColourText(av.getColourText());
1185       view.setShowFullId(av.getShowJVSuffix());
1186       view.setRightAlignIds(av.isRightAlignIds());
1187       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
1188       view.setShowText(av.getShowText());
1189       view.setShowUnconserved(av.getShowUnconserved());
1190       view.setWrapAlignment(av.getWrapAlignment());
1191       view.setTextCol1(av.getTextColour().getRGB());
1192       view.setTextCol2(av.getTextColour2().getRGB());
1193       view.setTextColThreshold(av.getThresholdTextColour());
1194       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
1195       view.setShowSequenceLogo(av.isShowSequenceLogo());
1196       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
1197       view.setShowGroupConsensus(av.isShowGroupConsensus());
1198       view.setShowGroupConservation(av.isShowGroupConservation());
1199       view.setShowNPfeatureTooltip(av.isShowNPFeats());
1200       view.setShowDbRefTooltip(av.isShowDBRefs());
1201       view.setFollowHighlight(av.isFollowHighlight());
1202       view.setFollowSelection(av.followSelection);
1203       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
1204       if (av.getFeaturesDisplayed() != null)
1205       {
1206         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
1207
1208         String[] renderOrder = ap.getSeqPanel().seqCanvas
1209                 .getFeatureRenderer().getRenderOrder()
1210                 .toArray(new String[0]);
1211
1212         Vector<String> settingsAdded = new Vector<String>();
1213         if (renderOrder != null)
1214         {
1215           for (String featureType : renderOrder)
1216           {
1217             FeatureColourI fcol = ap.getSeqPanel().seqCanvas
1218                     .getFeatureRenderer()
1219                     .getFeatureStyle(featureType);
1220             Setting setting = new Setting();
1221             setting.setType(featureType);
1222             if (!fcol.isSimpleColour())
1223             {
1224               setting.setColour(fcol.getMaxColour().getRGB());
1225               setting.setMincolour(fcol.getMinColour().getRGB());
1226               setting.setMin(fcol.getMin());
1227               setting.setMax(fcol.getMax());
1228               setting.setColourByLabel(fcol.isColourByLabel());
1229               setting.setAutoScale(fcol.isAutoScaled());
1230               setting.setThreshold(fcol.getThreshold());
1231               // -1 = No threshold, 0 = Below, 1 = Above
1232               setting.setThreshstate(fcol.isAboveThreshold() ? 1
1233                       : (fcol.isBelowThreshold() ? 0 : -1));
1234             }
1235             else
1236             {
1237               setting.setColour(fcol.getColour().getRGB());
1238             }
1239
1240             setting.setDisplay(av.getFeaturesDisplayed().isVisible(
1241                     featureType));
1242             float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1243                     .getOrder(featureType);
1244             if (rorder > -1)
1245             {
1246               setting.setOrder(rorder);
1247             }
1248             fs.addSetting(setting);
1249             settingsAdded.addElement(featureType);
1250           }
1251         }
1252
1253         // is groups actually supposed to be a map here ?
1254         Iterator<String> en = ap.getSeqPanel().seqCanvas
1255                 .getFeatureRenderer()
1256                 .getFeatureGroups().iterator();
1257         Vector<String> groupsAdded = new Vector<String>();
1258         while (en.hasNext())
1259         {
1260           String grp = en.next();
1261           if (groupsAdded.contains(grp))
1262           {
1263             continue;
1264           }
1265           Group g = new Group();
1266           g.setName(grp);
1267           g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
1268                   .getFeatureRenderer().checkGroupVisibility(grp, false))
1269                   .booleanValue());
1270           fs.addGroup(g);
1271           groupsAdded.addElement(grp);
1272         }
1273         jms.setFeatureSettings(fs);
1274       }
1275
1276       if (av.hasHiddenColumns())
1277       {
1278         if (av.getColumnSelection() == null
1279                 || av.getColumnSelection().getHiddenColumns() == null)
1280         {
1281           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1282         }
1283         else
1284         {
1285           for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
1286                   .size(); c++)
1287           {
1288             int[] region = av.getColumnSelection().getHiddenColumns()
1289                     .get(c);
1290             HiddenColumns hc = new HiddenColumns();
1291             hc.setStart(region[0]);
1292             hc.setEnd(region[1]);
1293             view.addHiddenColumns(hc);
1294           }
1295         }
1296       }
1297       if (calcIdSet.size() > 0)
1298       {
1299         for (String calcId : calcIdSet)
1300         {
1301           if (calcId.trim().length() > 0)
1302           {
1303             CalcIdParam cidp = createCalcIdParam(calcId, av);
1304             // Some calcIds have no parameters.
1305             if (cidp != null)
1306             {
1307               view.addCalcIdParam(cidp);
1308             }
1309           }
1310         }
1311       }
1312
1313       jms.addViewport(view);
1314     }
1315     object.setJalviewModelSequence(jms);
1316     object.getVamsasModel().addSequenceSet(vamsasSet);
1317
1318     if (jout != null && fileName != null)
1319     {
1320       // We may not want to write the object to disk,
1321       // eg we can copy the alignViewport to a new view object
1322       // using save and then load
1323       try
1324       {
1325         System.out.println("Writing jar entry " + fileName);
1326         JarEntry entry = new JarEntry(fileName);
1327         jout.putNextEntry(entry);
1328         PrintWriter pout = new PrintWriter(new OutputStreamWriter(jout,
1329                 UTF_8));
1330         Marshaller marshaller = new Marshaller(pout);
1331         marshaller.marshal(object);
1332         pout.flush();
1333         jout.closeEntry();
1334       } catch (Exception ex)
1335       {
1336         // TODO: raise error in GUI if marshalling failed.
1337         ex.printStackTrace();
1338       }
1339     }
1340     return object;
1341   }
1342
1343   /**
1344    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1345    * for each viewer, with
1346    * <ul>
1347    * <li>viewer geometry (position, size, split pane divider location)</li>
1348    * <li>index of the selected structure in the viewer (currently shows gapped
1349    * or ungapped)</li>
1350    * <li>the id of the annotation holding RNA secondary structure</li>
1351    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1352    * </ul>
1353    * Varna viewer state is also written out (in native Varna XML) to separate
1354    * project jar entries. A separate entry is written for each RNA structure
1355    * displayed, with the naming convention
1356    * <ul>
1357    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1358    * </ul>
1359    * 
1360    * @param jout
1361    * @param jseq
1362    * @param jds
1363    * @param viewIds
1364    * @param ap
1365    * @param storeDataset
1366    */
1367   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1368           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1369           boolean storeDataset)
1370   {
1371     if (Desktop.desktop == null)
1372     {
1373       return;
1374     }
1375     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1376     for (int f = frames.length - 1; f > -1; f--)
1377     {
1378       if (frames[f] instanceof AppVarna)
1379       {
1380         AppVarna varna = (AppVarna) frames[f];
1381         /*
1382          * link the sequence to every viewer that is showing it and is linked to
1383          * its alignment panel
1384          */
1385         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1386         {
1387           String viewId = varna.getViewId();
1388           RnaViewer rna = new RnaViewer();
1389           rna.setViewId(viewId);
1390           rna.setTitle(varna.getTitle());
1391           rna.setXpos(varna.getX());
1392           rna.setYpos(varna.getY());
1393           rna.setWidth(varna.getWidth());
1394           rna.setHeight(varna.getHeight());
1395           rna.setDividerLocation(varna.getDividerLocation());
1396           rna.setSelectedRna(varna.getSelectedIndex());
1397           jseq.addRnaViewer(rna);
1398
1399           /*
1400            * Store each Varna panel's state once in the project per sequence.
1401            * First time through only (storeDataset==false)
1402            */
1403           // boolean storeSessions = false;
1404           // String sequenceViewId = viewId + seqsToIds.get(jds);
1405           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1406           // {
1407           // viewIds.add(sequenceViewId);
1408           // storeSessions = true;
1409           // }
1410           for (RnaModel model : varna.getModels())
1411           {
1412             if (model.seq == jds)
1413             {
1414               /*
1415                * VARNA saves each view (sequence or alignment secondary
1416                * structure, gapped or trimmed) as a separate XML file
1417                */
1418               String jarEntryName = rnaSessions.get(model);
1419               if (jarEntryName == null)
1420               {
1421
1422                 String varnaStateFile = varna.getStateInfo(model.rna);
1423                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1424                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1425                 rnaSessions.put(model, jarEntryName);
1426               }
1427               SecondaryStructure ss = new SecondaryStructure();
1428               String annotationId = varna.getAnnotation(jds).annotationId;
1429               ss.setAnnotationId(annotationId);
1430               ss.setViewerState(jarEntryName);
1431               ss.setGapped(model.gapped);
1432               ss.setTitle(model.title);
1433               rna.addSecondaryStructure(ss);
1434             }
1435           }
1436         }
1437       }
1438     }
1439   }
1440
1441   /**
1442    * Copy the contents of a file to a new entry added to the output jar
1443    * 
1444    * @param jout
1445    * @param infilePath
1446    * @param jarEntryName
1447    */
1448   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1449           String jarEntryName)
1450   {
1451     DataInputStream dis = null;
1452     try
1453     {
1454       File file = new File(infilePath);
1455       if (file.exists() && jout != null)
1456       {
1457         dis = new DataInputStream(new FileInputStream(file));
1458         byte[] data = new byte[(int) file.length()];
1459         dis.readFully(data);
1460         writeJarEntry(jout, jarEntryName, data);
1461       }
1462     } catch (Exception ex)
1463     {
1464       ex.printStackTrace();
1465     } finally
1466     {
1467       if (dis != null)
1468       {
1469         try
1470         {
1471           dis.close();
1472         } catch (IOException e)
1473         {
1474           // ignore
1475         }
1476       }
1477     }
1478   }
1479
1480   /**
1481    * Write the data to a new entry of given name in the output jar file
1482    * 
1483    * @param jout
1484    * @param jarEntryName
1485    * @param data
1486    * @throws IOException
1487    */
1488   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
1489           byte[] data) throws IOException
1490   {
1491     if (jout != null)
1492     {
1493       System.out.println("Writing jar entry " + jarEntryName);
1494       jout.putNextEntry(new JarEntry(jarEntryName));
1495       DataOutputStream dout = new DataOutputStream(jout);
1496       dout.write(data, 0, data.length);
1497       dout.flush();
1498       jout.closeEntry();
1499     }
1500   }
1501
1502   /**
1503    * Save the state of a structure viewer
1504    * 
1505    * @param ap
1506    * @param jds
1507    * @param pdb
1508    *          the archive XML element under which to save the state
1509    * @param entry
1510    * @param viewIds
1511    * @param matchedFile
1512    * @param viewFrame
1513    * @return
1514    */
1515   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
1516           Pdbids pdb, PDBEntry entry, List<String> viewIds,
1517           String matchedFile, StructureViewerBase viewFrame)
1518   {
1519     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
1520
1521     /*
1522      * Look for any bindings for this viewer to the PDB file of interest
1523      * (including part matches excluding chain id)
1524      */
1525     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
1526     {
1527       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
1528       final String pdbId = pdbentry.getId();
1529       if (!pdbId.equals(entry.getId())
1530               && !(entry.getId().length() > 4 && entry.getId()
1531                       .toLowerCase().startsWith(pdbId.toLowerCase())))
1532       {
1533         /*
1534          * not interested in a binding to a different PDB entry here
1535          */
1536         continue;
1537       }
1538       if (matchedFile == null)
1539       {
1540         matchedFile = pdbentry.getFile();
1541       }
1542       else if (!matchedFile.equals(pdbentry.getFile()))
1543       {
1544         Cache.log
1545                 .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
1546                         + pdbentry.getFile());
1547       }
1548       // record the
1549       // file so we
1550       // can get at it if the ID
1551       // match is ambiguous (e.g.
1552       // 1QIP==1qipA)
1553
1554       for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
1555       {
1556         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
1557         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
1558         {
1559           StructureState state = new StructureState();
1560           state.setVisible(true);
1561           state.setXpos(viewFrame.getX());
1562           state.setYpos(viewFrame.getY());
1563           state.setWidth(viewFrame.getWidth());
1564           state.setHeight(viewFrame.getHeight());
1565           final String viewId = viewFrame.getViewId();
1566           state.setViewId(viewId);
1567           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
1568           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
1569           state.setColourByJmol(viewFrame.isColouredByViewer());
1570           state.setType(viewFrame.getViewerType().toString());
1571           pdb.addStructureState(state);
1572         }
1573       }
1574     }
1575     return matchedFile;
1576   }
1577
1578   private AnnotationColours constructAnnotationColours(
1579           AnnotationColourGradient acg, List<UserColourScheme> userColours,
1580           JalviewModelSequence jms)
1581   {
1582     AnnotationColours ac = new AnnotationColours();
1583     ac.setAboveThreshold(acg.getAboveThreshold());
1584     ac.setThreshold(acg.getAnnotationThreshold());
1585     ac.setAnnotation(acg.getAnnotation());
1586     if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
1587     {
1588       ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
1589               userColours, jms));
1590     }
1591     else
1592     {
1593       ac.setColourScheme(ColourSchemeProperty.getColourName(acg
1594               .getBaseColour()));
1595     }
1596
1597     ac.setMaxColour(acg.getMaxColour().getRGB());
1598     ac.setMinColour(acg.getMinColour().getRGB());
1599     ac.setPerSequence(acg.isSeqAssociated());
1600     ac.setPredefinedColours(acg.isPredefinedColours());
1601     return ac;
1602   }
1603
1604   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
1605           IdentityHashMap<SequenceGroup, String> groupRefs,
1606           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
1607           SequenceSet vamsasSet)
1608   {
1609
1610     for (int i = 0; i < aa.length; i++)
1611     {
1612       Annotation an = new Annotation();
1613
1614       AlignmentAnnotation annotation = aa[i];
1615       if (annotation.annotationId != null)
1616       {
1617         annotationIds.put(annotation.annotationId, annotation);
1618       }
1619
1620       an.setId(annotation.annotationId);
1621
1622       an.setVisible(annotation.visible);
1623
1624       an.setDescription(annotation.description);
1625
1626       if (annotation.sequenceRef != null)
1627       {
1628         // 2.9 JAL-1781 xref on sequence id rather than name
1629         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
1630       }
1631       if (annotation.groupRef != null)
1632       {
1633         String groupIdr = groupRefs.get(annotation.groupRef);
1634         if (groupIdr == null)
1635         {
1636           // make a locally unique String
1637           groupRefs.put(
1638                   annotation.groupRef,
1639                   groupIdr = ("" + System.currentTimeMillis()
1640                           + annotation.groupRef.getName() + groupRefs
1641                           .size()));
1642         }
1643         an.setGroupRef(groupIdr.toString());
1644       }
1645
1646       // store all visualization attributes for annotation
1647       an.setGraphHeight(annotation.graphHeight);
1648       an.setCentreColLabels(annotation.centreColLabels);
1649       an.setScaleColLabels(annotation.scaleColLabel);
1650       an.setShowAllColLabels(annotation.showAllColLabels);
1651       an.setBelowAlignment(annotation.belowAlignment);
1652
1653       if (annotation.graph > 0)
1654       {
1655         an.setGraph(true);
1656         an.setGraphType(annotation.graph);
1657         an.setGraphGroup(annotation.graphGroup);
1658         if (annotation.getThreshold() != null)
1659         {
1660           ThresholdLine line = new ThresholdLine();
1661           line.setLabel(annotation.getThreshold().label);
1662           line.setValue(annotation.getThreshold().value);
1663           line.setColour(annotation.getThreshold().colour.getRGB());
1664           an.setThresholdLine(line);
1665         }
1666       }
1667       else
1668       {
1669         an.setGraph(false);
1670       }
1671
1672       an.setLabel(annotation.label);
1673
1674       if (annotation == av.getAlignmentQualityAnnot()
1675               || annotation == av.getAlignmentConservationAnnotation()
1676               || annotation == av.getAlignmentConsensusAnnotation()
1677               || annotation.autoCalculated)
1678       {
1679         // new way of indicating autocalculated annotation -
1680         an.setAutoCalculated(annotation.autoCalculated);
1681       }
1682       if (annotation.hasScore())
1683       {
1684         an.setScore(annotation.getScore());
1685       }
1686
1687       if (annotation.getCalcId() != null)
1688       {
1689         calcIdSet.add(annotation.getCalcId());
1690         an.setCalcId(annotation.getCalcId());
1691       }
1692       if (annotation.hasProperties())
1693       {
1694         for (String pr : annotation.getProperties())
1695         {
1696           Property prop = new Property();
1697           prop.setName(pr);
1698           prop.setValue(annotation.getProperty(pr));
1699           an.addProperty(prop);
1700         }
1701       }
1702
1703       AnnotationElement ae;
1704       if (annotation.annotations != null)
1705       {
1706         an.setScoreOnly(false);
1707         for (int a = 0; a < annotation.annotations.length; a++)
1708         {
1709           if ((annotation == null) || (annotation.annotations[a] == null))
1710           {
1711             continue;
1712           }
1713
1714           ae = new AnnotationElement();
1715           if (annotation.annotations[a].description != null)
1716           {
1717             ae.setDescription(annotation.annotations[a].description);
1718           }
1719           if (annotation.annotations[a].displayCharacter != null)
1720           {
1721             ae.setDisplayCharacter(annotation.annotations[a].displayCharacter);
1722           }
1723
1724           if (!Float.isNaN(annotation.annotations[a].value))
1725           {
1726             ae.setValue(annotation.annotations[a].value);
1727           }
1728
1729           ae.setPosition(a);
1730           if (annotation.annotations[a].secondaryStructure > ' ')
1731           {
1732             ae.setSecondaryStructure(annotation.annotations[a].secondaryStructure
1733                     + "");
1734           }
1735
1736           if (annotation.annotations[a].colour != null
1737                   && annotation.annotations[a].colour != java.awt.Color.black)
1738           {
1739             ae.setColour(annotation.annotations[a].colour.getRGB());
1740           }
1741
1742           an.addAnnotationElement(ae);
1743           if (annotation.autoCalculated)
1744           {
1745             // only write one non-null entry into the annotation row -
1746             // sufficient to get the visualization attributes necessary to
1747             // display data
1748             continue;
1749           }
1750         }
1751       }
1752       else
1753       {
1754         an.setScoreOnly(true);
1755       }
1756       if (!storeDS || (storeDS && !annotation.autoCalculated))
1757       {
1758         // skip autocalculated annotation - these are only provided for
1759         // alignments
1760         vamsasSet.addAnnotation(an);
1761       }
1762     }
1763
1764   }
1765
1766   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
1767   {
1768     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
1769     if (settings != null)
1770     {
1771       CalcIdParam vCalcIdParam = new CalcIdParam();
1772       vCalcIdParam.setCalcId(calcId);
1773       vCalcIdParam.addServiceURL(settings.getServiceURI());
1774       // generic URI allowing a third party to resolve another instance of the
1775       // service used for this calculation
1776       for (String urls : settings.getServiceURLs())
1777       {
1778         vCalcIdParam.addServiceURL(urls);
1779       }
1780       vCalcIdParam.setVersion("1.0");
1781       if (settings.getPreset() != null)
1782       {
1783         WsParamSetI setting = settings.getPreset();
1784         vCalcIdParam.setName(setting.getName());
1785         vCalcIdParam.setDescription(setting.getDescription());
1786       }
1787       else
1788       {
1789         vCalcIdParam.setName("");
1790         vCalcIdParam.setDescription("Last used parameters");
1791       }
1792       // need to be able to recover 1) settings 2) user-defined presets or
1793       // recreate settings from preset 3) predefined settings provided by
1794       // service - or settings that can be transferred (or discarded)
1795       vCalcIdParam.setParameters(settings.getWsParamFile().replace("\n",
1796               "|\\n|"));
1797       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
1798       // todo - decide if updateImmediately is needed for any projects.
1799
1800       return vCalcIdParam;
1801     }
1802     return null;
1803   }
1804
1805   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
1806           AlignViewport av)
1807   {
1808     if (calcIdParam.getVersion().equals("1.0"))
1809     {
1810       Jws2Instance service = Jws2Discoverer.getDiscoverer()
1811               .getPreferredServiceFor(calcIdParam.getServiceURL());
1812       if (service != null)
1813       {
1814         WsParamSetI parmSet = null;
1815         try
1816         {
1817           parmSet = service.getParamStore().parseServiceParameterFile(
1818                   calcIdParam.getName(), calcIdParam.getDescription(),
1819                   calcIdParam.getServiceURL(),
1820                   calcIdParam.getParameters().replace("|\\n|", "\n"));
1821         } catch (IOException x)
1822         {
1823           warn("Couldn't parse parameter data for "
1824                   + calcIdParam.getCalcId(), x);
1825           return false;
1826         }
1827         List<ArgumentI> argList = null;
1828         if (calcIdParam.getName().length() > 0)
1829         {
1830           parmSet = service.getParamStore()
1831                   .getPreset(calcIdParam.getName());
1832           if (parmSet != null)
1833           {
1834             // TODO : check we have a good match with settings in AACon -
1835             // otherwise we'll need to create a new preset
1836           }
1837         }
1838         else
1839         {
1840           argList = parmSet.getArguments();
1841           parmSet = null;
1842         }
1843         AAConSettings settings = new AAConSettings(
1844                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
1845         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
1846                 calcIdParam.isNeedsUpdate());
1847         return true;
1848       }
1849       else
1850       {
1851         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
1852         return false;
1853       }
1854     }
1855     throw new Error(MessageManager.formatMessage(
1856             "error.unsupported_version_calcIdparam",
1857             new Object[] { calcIdParam.toString() }));
1858   }
1859
1860   /**
1861    * External mapping between jalview objects and objects yielding a valid and
1862    * unique object ID string. This is null for normal Jalview project IO, but
1863    * non-null when a jalview project is being read or written as part of a
1864    * vamsas session.
1865    */
1866   IdentityHashMap jv2vobj = null;
1867
1868   /**
1869    * Construct a unique ID for jvobj using either existing bindings or if none
1870    * exist, the result of the hashcode call for the object.
1871    * 
1872    * @param jvobj
1873    *          jalview data object
1874    * @return unique ID for referring to jvobj
1875    */
1876   private String makeHashCode(Object jvobj, String altCode)
1877   {
1878     if (jv2vobj != null)
1879     {
1880       Object id = jv2vobj.get(jvobj);
1881       if (id != null)
1882       {
1883         return id.toString();
1884       }
1885       // check string ID mappings
1886       if (jvids2vobj != null && jvobj instanceof String)
1887       {
1888         id = jvids2vobj.get(jvobj);
1889       }
1890       if (id != null)
1891       {
1892         return id.toString();
1893       }
1894       // give up and warn that something has gone wrong
1895       warn("Cannot find ID for object in external mapping : " + jvobj);
1896     }
1897     return altCode;
1898   }
1899
1900   /**
1901    * return local jalview object mapped to ID, if it exists
1902    * 
1903    * @param idcode
1904    *          (may be null)
1905    * @return null or object bound to idcode
1906    */
1907   private Object retrieveExistingObj(String idcode)
1908   {
1909     if (idcode != null && vobj2jv != null)
1910     {
1911       return vobj2jv.get(idcode);
1912     }
1913     return null;
1914   }
1915
1916   /**
1917    * binding from ID strings from external mapping table to jalview data model
1918    * objects.
1919    */
1920   private Hashtable vobj2jv;
1921
1922   private Sequence createVamsasSequence(String id, SequenceI jds)
1923   {
1924     return createVamsasSequence(true, id, jds, null);
1925   }
1926
1927   private Sequence createVamsasSequence(boolean recurse, String id,
1928           SequenceI jds, SequenceI parentseq)
1929   {
1930     Sequence vamsasSeq = new Sequence();
1931     vamsasSeq.setId(id);
1932     vamsasSeq.setName(jds.getName());
1933     vamsasSeq.setSequence(jds.getSequenceAsString());
1934     vamsasSeq.setDescription(jds.getDescription());
1935     jalview.datamodel.DBRefEntry[] dbrefs = null;
1936     if (jds.getDatasetSequence() != null)
1937     {
1938       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
1939       if (jds.getDatasetSequence().getDBRefs() != null)
1940       {
1941         dbrefs = jds.getDatasetSequence().getDBRefs();
1942       }
1943     }
1944     else
1945     {
1946       vamsasSeq.setDsseqid(id); // so we can tell which sequences really are
1947       // dataset sequences only
1948       dbrefs = jds.getDBRefs();
1949     }
1950     if (dbrefs != null)
1951     {
1952       for (int d = 0; d < dbrefs.length; d++)
1953       {
1954         DBRef dbref = new DBRef();
1955         dbref.setSource(dbrefs[d].getSource());
1956         dbref.setVersion(dbrefs[d].getVersion());
1957         dbref.setAccessionId(dbrefs[d].getAccessionId());
1958         if (dbrefs[d].hasMap())
1959         {
1960           Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
1961                   jds, recurse);
1962           dbref.setMapping(mp);
1963         }
1964         vamsasSeq.addDBRef(dbref);
1965       }
1966     }
1967     return vamsasSeq;
1968   }
1969
1970   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
1971           SequenceI parentseq, SequenceI jds, boolean recurse)
1972   {
1973     Mapping mp = null;
1974     if (jmp.getMap() != null)
1975     {
1976       mp = new Mapping();
1977
1978       jalview.util.MapList mlst = jmp.getMap();
1979       List<int[]> r = mlst.getFromRanges();
1980       for (int[] range : r)
1981       {
1982         MapListFrom mfrom = new MapListFrom();
1983         mfrom.setStart(range[0]);
1984         mfrom.setEnd(range[1]);
1985         mp.addMapListFrom(mfrom);
1986       }
1987       r = mlst.getToRanges();
1988       for (int[] range : r)
1989       {
1990         MapListTo mto = new MapListTo();
1991         mto.setStart(range[0]);
1992         mto.setEnd(range[1]);
1993         mp.addMapListTo(mto);
1994       }
1995       mp.setMapFromUnit(mlst.getFromRatio());
1996       mp.setMapToUnit(mlst.getToRatio());
1997       if (jmp.getTo() != null)
1998       {
1999         MappingChoice mpc = new MappingChoice();
2000         if (recurse
2001                 && (parentseq != jmp.getTo() || parentseq
2002                         .getDatasetSequence() != jmp.getTo()))
2003         {
2004           mpc.setSequence(createVamsasSequence(false, seqHash(jmp.getTo()),
2005                   jmp.getTo(), jds));
2006         }
2007         else
2008         {
2009           String jmpid = "";
2010           SequenceI ps = null;
2011           if (parentseq != jmp.getTo()
2012                   && parentseq.getDatasetSequence() != jmp.getTo())
2013           {
2014             // chaining dbref rather than a handshaking one
2015             jmpid = seqHash(ps = jmp.getTo());
2016           }
2017           else
2018           {
2019             jmpid = seqHash(ps = parentseq);
2020           }
2021           mpc.setDseqFor(jmpid);
2022           if (!seqRefIds.containsKey(mpc.getDseqFor()))
2023           {
2024             jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2025             seqRefIds.put(mpc.getDseqFor(), ps);
2026           }
2027           else
2028           {
2029             jalview.bin.Cache.log.debug("reusing DseqFor ID");
2030           }
2031         }
2032         mp.setMappingChoice(mpc);
2033       }
2034     }
2035     return mp;
2036   }
2037
2038   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2039           List<UserColourScheme> userColours, JalviewModelSequence jms)
2040   {
2041     String id = null;
2042     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2043     boolean newucs = false;
2044     if (!userColours.contains(ucs))
2045     {
2046       userColours.add(ucs);
2047       newucs = true;
2048     }
2049     id = "ucs" + userColours.indexOf(ucs);
2050     if (newucs)
2051     {
2052       // actually create the scheme's entry in the XML model
2053       java.awt.Color[] colours = ucs.getColours();
2054       jalview.schemabinding.version2.UserColours uc = new jalview.schemabinding.version2.UserColours();
2055       jalview.schemabinding.version2.UserColourScheme jbucs = new jalview.schemabinding.version2.UserColourScheme();
2056
2057       for (int i = 0; i < colours.length; i++)
2058       {
2059         jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2060         col.setName(ResidueProperties.aa[i]);
2061         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2062         jbucs.addColour(col);
2063       }
2064       if (ucs.getLowerCaseColours() != null)
2065       {
2066         colours = ucs.getLowerCaseColours();
2067         for (int i = 0; i < colours.length; i++)
2068         {
2069           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2070           col.setName(ResidueProperties.aa[i].toLowerCase());
2071           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2072           jbucs.addColour(col);
2073         }
2074       }
2075
2076       uc.setId(id);
2077       uc.setUserColourScheme(jbucs);
2078       jms.addUserColours(uc);
2079     }
2080
2081     return id;
2082   }
2083
2084   jalview.schemes.UserColourScheme getUserColourScheme(
2085           JalviewModelSequence jms, String id)
2086   {
2087     UserColours[] uc = jms.getUserColours();
2088     UserColours colours = null;
2089
2090     for (int i = 0; i < uc.length; i++)
2091     {
2092       if (uc[i].getId().equals(id))
2093       {
2094         colours = uc[i];
2095
2096         break;
2097       }
2098     }
2099
2100     java.awt.Color[] newColours = new java.awt.Color[24];
2101
2102     for (int i = 0; i < 24; i++)
2103     {
2104       newColours[i] = new java.awt.Color(Integer.parseInt(colours
2105               .getUserColourScheme().getColour(i).getRGB(), 16));
2106     }
2107
2108     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2109             newColours);
2110
2111     if (colours.getUserColourScheme().getColourCount() > 24)
2112     {
2113       newColours = new java.awt.Color[23];
2114       for (int i = 0; i < 23; i++)
2115       {
2116         newColours[i] = new java.awt.Color(Integer.parseInt(colours
2117                 .getUserColourScheme().getColour(i + 24).getRGB(), 16));
2118       }
2119       ucs.setLowerCaseColours(newColours);
2120     }
2121
2122     return ucs;
2123   }
2124
2125   /**
2126    * contains last error message (if any) encountered by XML loader.
2127    */
2128   String errorMessage = null;
2129
2130   /**
2131    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2132    * exceptions are raised during project XML parsing
2133    */
2134   public boolean attemptversion1parse = true;
2135
2136   /**
2137    * Load a jalview project archive from a jar file
2138    * 
2139    * @param file
2140    *          - HTTP URL or filename
2141    */
2142   public AlignFrame loadJalviewAlign(final String file)
2143   {
2144
2145     jalview.gui.AlignFrame af = null;
2146
2147     try
2148     {
2149       // create list to store references for any new Jmol viewers created
2150       newStructureViewers = new Vector<JalviewStructureDisplayI>();
2151       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2152       // Workaround is to make sure caller implements the JarInputStreamProvider
2153       // interface
2154       // so we can re-open the jar input stream for each entry.
2155
2156       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2157       af = loadJalviewAlign(jprovider);
2158
2159     } catch (MalformedURLException e)
2160     {
2161       errorMessage = "Invalid URL format for '" + file + "'";
2162       reportErrors();
2163     } finally
2164     {
2165       try
2166       {
2167         SwingUtilities.invokeAndWait(new Runnable()
2168         {
2169           @Override
2170           public void run()
2171           {
2172             setLoadingFinishedForNewStructureViewers();
2173           };
2174         });
2175       } catch (Exception x)
2176       {
2177         System.err.println("Error loading alignment: " + x.getMessage());
2178       }
2179     }
2180     return af;
2181   }
2182
2183   private jarInputStreamProvider createjarInputStreamProvider(
2184           final String file) throws MalformedURLException
2185   {
2186     URL url = null;
2187     errorMessage = null;
2188     uniqueSetSuffix = null;
2189     seqRefIds = null;
2190     viewportsAdded.clear();
2191     frefedSequence = null;
2192
2193     if (file.startsWith("http://"))
2194     {
2195       url = new URL(file);
2196     }
2197     final URL _url = url;
2198     return new jarInputStreamProvider()
2199     {
2200
2201       @Override
2202       public JarInputStream getJarInputStream() throws IOException
2203       {
2204         if (_url != null)
2205         {
2206           return new JarInputStream(_url.openStream());
2207         }
2208         else
2209         {
2210           return new JarInputStream(new FileInputStream(file));
2211         }
2212       }
2213
2214       @Override
2215       public String getFilename()
2216       {
2217         return file;
2218       }
2219     };
2220   }
2221
2222   /**
2223    * Recover jalview session from a jalview project archive. Caller may
2224    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2225    * themselves. Any null fields will be initialised with default values,
2226    * non-null fields are left alone.
2227    * 
2228    * @param jprovider
2229    * @return
2230    */
2231   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2232   {
2233     errorMessage = null;
2234     if (uniqueSetSuffix == null)
2235     {
2236       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2237     }
2238     if (seqRefIds == null)
2239     {
2240       seqRefIds = new HashMap<String, SequenceI>();
2241     }
2242     if (frefedSequence == null)
2243     {
2244       frefedSequence = new Vector<Object[]>();
2245     }
2246
2247     AlignFrame af = null, _af = null;
2248     Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
2249     final String file = jprovider.getFilename();
2250     try
2251     {
2252       JarInputStream jin = null;
2253       JarEntry jarentry = null;
2254       int entryCount = 1;
2255
2256       do
2257       {
2258         jin = jprovider.getJarInputStream();
2259         for (int i = 0; i < entryCount; i++)
2260         {
2261           jarentry = jin.getNextJarEntry();
2262         }
2263
2264         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2265         {
2266           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2267           JalviewModel object = new JalviewModel();
2268
2269           Unmarshaller unmar = new Unmarshaller(object);
2270           unmar.setValidation(false);
2271           object = (JalviewModel) unmar.unmarshal(in);
2272           if (true) // !skipViewport(object))
2273           {
2274             _af = loadFromObject(object, file, true, jprovider);
2275             if (object.getJalviewModelSequence().getViewportCount() > 0)
2276             {
2277               af = _af;
2278               if (af.viewport.isGatherViewsHere())
2279               {
2280                 gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
2281               }
2282             }
2283           }
2284           entryCount++;
2285         }
2286         else if (jarentry != null)
2287         {
2288           // Some other file here.
2289           entryCount++;
2290         }
2291       } while (jarentry != null);
2292       resolveFrefedSequences();
2293     } catch (IOException ex)
2294     {
2295       ex.printStackTrace();
2296       errorMessage = "Couldn't locate Jalview XML file : " + file;
2297       System.err.println("Exception whilst loading jalview XML file : "
2298               + ex + "\n");
2299     } catch (Exception ex)
2300     {
2301       System.err.println("Parsing as Jalview Version 2 file failed.");
2302       ex.printStackTrace(System.err);
2303       if (attemptversion1parse)
2304       {
2305         // Is Version 1 Jar file?
2306         try
2307         {
2308           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2309         } catch (Exception ex2)
2310         {
2311           System.err.println("Exception whilst loading as jalviewXMLV1:");
2312           ex2.printStackTrace();
2313           af = null;
2314         }
2315       }
2316       if (Desktop.instance != null)
2317       {
2318         Desktop.instance.stopLoading();
2319       }
2320       if (af != null)
2321       {
2322         System.out.println("Successfully loaded archive file");
2323         return af;
2324       }
2325       ex.printStackTrace();
2326
2327       System.err.println("Exception whilst loading jalview XML file : "
2328               + ex + "\n");
2329     } catch (OutOfMemoryError e)
2330     {
2331       // Don't use the OOM Window here
2332       errorMessage = "Out of memory loading jalview XML file";
2333       System.err.println("Out of memory whilst loading jalview XML file");
2334       e.printStackTrace();
2335     }
2336
2337     if (Desktop.instance != null)
2338     {
2339       Desktop.instance.stopLoading();
2340     }
2341
2342     /*
2343      * Regather multiple views (with the same sequence set id) to the frame (if
2344      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2345      * views instead of separate frames. Note this doesn't restore a state where
2346      * some expanded views in turn have tabbed views - the last "first tab" read
2347      * in will play the role of gatherer for all.
2348      */
2349     for (AlignFrame fr : gatherToThisFrame.values())
2350     {
2351       Desktop.instance.gatherViews(fr);
2352     }
2353
2354     restoreSplitFrames();
2355
2356     if (errorMessage != null)
2357     {
2358       reportErrors();
2359     }
2360     return af;
2361   }
2362
2363   /**
2364    * Try to reconstruct and display SplitFrame windows, where each contains
2365    * complementary dna and protein alignments. Done by pairing up AlignFrame
2366    * objects (created earlier) which have complementary viewport ids associated.
2367    */
2368   protected void restoreSplitFrames()
2369   {
2370     List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
2371     List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
2372     Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
2373
2374     /*
2375      * Identify the DNA alignments
2376      */
2377     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2378             .entrySet())
2379     {
2380       AlignFrame af = candidate.getValue();
2381       if (af.getViewport().getAlignment().isNucleotide())
2382       {
2383         dna.put(candidate.getKey().getId(), af);
2384       }
2385     }
2386
2387     /*
2388      * Try to match up the protein complements
2389      */
2390     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2391             .entrySet())
2392     {
2393       AlignFrame af = candidate.getValue();
2394       if (!af.getViewport().getAlignment().isNucleotide())
2395       {
2396         String complementId = candidate.getKey().getComplementId();
2397         // only non-null complements should be in the Map
2398         if (complementId != null && dna.containsKey(complementId))
2399         {
2400           final AlignFrame dnaFrame = dna.get(complementId);
2401           SplitFrame sf = createSplitFrame(dnaFrame, af);
2402           addedToSplitFrames.add(dnaFrame);
2403           addedToSplitFrames.add(af);
2404           if (af.viewport.isGatherViewsHere())
2405           {
2406             gatherTo.add(sf);
2407           }
2408         }
2409       }
2410     }
2411
2412     /*
2413      * Open any that we failed to pair up (which shouldn't happen!) as
2414      * standalone AlignFrame's.
2415      */
2416     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2417             .entrySet())
2418     {
2419       AlignFrame af = candidate.getValue();
2420       if (!addedToSplitFrames.contains(af))
2421       {
2422         Viewport view = candidate.getKey();
2423         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2424                 view.getHeight());
2425         System.err.println("Failed to restore view " + view.getTitle()
2426                 + " to split frame");
2427       }
2428     }
2429
2430     /*
2431      * Gather back into tabbed views as flagged.
2432      */
2433     for (SplitFrame sf : gatherTo)
2434     {
2435       Desktop.instance.gatherViews(sf);
2436     }
2437
2438     splitFrameCandidates.clear();
2439   }
2440
2441   /**
2442    * Construct and display one SplitFrame holding DNA and protein alignments.
2443    * 
2444    * @param dnaFrame
2445    * @param proteinFrame
2446    * @return
2447    */
2448   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2449           AlignFrame proteinFrame)
2450   {
2451     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2452     String title = MessageManager.getString("label.linked_view_title");
2453     int width = (int) dnaFrame.getBounds().getWidth();
2454     int height = (int) (dnaFrame.getBounds().getHeight()
2455             + proteinFrame.getBounds().getHeight() + 50);
2456
2457     /*
2458      * SplitFrame location is saved to both enclosed frames
2459      */
2460     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
2461     Desktop.addInternalFrame(splitFrame, title, width, height);
2462
2463     /*
2464      * And compute cDNA consensus (couldn't do earlier with consensus as
2465      * mappings were not yet present)
2466      */
2467     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2468
2469     return splitFrame;
2470   }
2471
2472   /**
2473    * check errorMessage for a valid error message and raise an error box in the
2474    * GUI or write the current errorMessage to stderr and then clear the error
2475    * state.
2476    */
2477   protected void reportErrors()
2478   {
2479     reportErrors(false);
2480   }
2481
2482   protected void reportErrors(final boolean saving)
2483   {
2484     if (errorMessage != null)
2485     {
2486       final String finalErrorMessage = errorMessage;
2487       if (raiseGUI)
2488       {
2489         javax.swing.SwingUtilities.invokeLater(new Runnable()
2490         {
2491           @Override
2492           public void run()
2493           {
2494             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2495                     finalErrorMessage, "Error "
2496                             + (saving ? "saving" : "loading")
2497                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2498           }
2499         });
2500       }
2501       else
2502       {
2503         System.err.println("Problem loading Jalview file: " + errorMessage);
2504       }
2505     }
2506     errorMessage = null;
2507   }
2508
2509   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2510
2511   /**
2512    * when set, local views will be updated from view stored in JalviewXML
2513    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2514    * sync if this is set to true.
2515    */
2516   private final boolean updateLocalViews = false;
2517
2518   /**
2519    * Returns the path to a temporary file holding the PDB file for the given PDB
2520    * id. The first time of asking, searches for a file of that name in the
2521    * Jalview project jar, and copies it to a new temporary file. Any repeat
2522    * requests just return the path to the file previously created.
2523    * 
2524    * @param jprovider
2525    * @param pdbId
2526    * @return
2527    */
2528   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
2529   {
2530     if (alreadyLoadedPDB.containsKey(pdbId))
2531     {
2532       return alreadyLoadedPDB.get(pdbId).toString();
2533     }
2534
2535     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
2536     if (tempFile != null)
2537     {
2538       alreadyLoadedPDB.put(pdbId, tempFile);
2539     }
2540     return tempFile;
2541   }
2542
2543   /**
2544    * Copies the jar entry of given name to a new temporary file and returns the
2545    * path to the file, or null if the entry is not found.
2546    * 
2547    * @param jprovider
2548    * @param jarEntryName
2549    * @param prefix
2550    *          a prefix for the temporary file name, must be at least three
2551    *          characters long
2552    * @return
2553    */
2554   protected String copyJarEntry(jarInputStreamProvider jprovider,
2555           String jarEntryName, String prefix)
2556   {
2557     BufferedReader in = null;
2558     PrintWriter out = null;
2559
2560     try
2561     {
2562       JarInputStream jin = jprovider.getJarInputStream();
2563       /*
2564        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2565        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2566        * FileInputStream(jprovider)); }
2567        */
2568
2569       JarEntry entry = null;
2570       do
2571       {
2572         entry = jin.getNextJarEntry();
2573       } while (entry != null && !entry.getName().equals(jarEntryName));
2574       if (entry != null)
2575       {
2576         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2577         File outFile = File.createTempFile(prefix, ".tmp");
2578         outFile.deleteOnExit();
2579         out = new PrintWriter(new FileOutputStream(outFile));
2580         String data;
2581
2582         while ((data = in.readLine()) != null)
2583         {
2584           out.println(data);
2585         }
2586         out.flush();
2587         String t = outFile.getAbsolutePath();
2588         return t;
2589       }
2590       else
2591       {
2592         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2593       }
2594     } catch (Exception ex)
2595     {
2596       ex.printStackTrace();
2597     } finally
2598     {
2599       if (in != null)
2600       {
2601         try
2602         {
2603           in.close();
2604         } catch (IOException e)
2605         {
2606           // ignore
2607         }
2608       }
2609       if (out != null)
2610       {
2611         out.close();
2612       }
2613     }
2614
2615     return null;
2616   }
2617
2618   private class JvAnnotRow
2619   {
2620     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2621     {
2622       order = i;
2623       template = jaa;
2624     }
2625
2626     /**
2627      * persisted version of annotation row from which to take vis properties
2628      */
2629     public jalview.datamodel.AlignmentAnnotation template;
2630
2631     /**
2632      * original position of the annotation row in the alignment
2633      */
2634     public int order;
2635   }
2636
2637   /**
2638    * Load alignment frame from jalview XML DOM object
2639    * 
2640    * @param object
2641    *          DOM
2642    * @param file
2643    *          filename source string
2644    * @param loadTreesAndStructures
2645    *          when false only create Viewport
2646    * @param jprovider
2647    *          data source provider
2648    * @return alignment frame created from view stored in DOM
2649    */
2650   AlignFrame loadFromObject(JalviewModel object, String file,
2651           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2652   {
2653     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2654     Sequence[] vamsasSeq = vamsasSet.getSequence();
2655
2656     JalviewModelSequence jms = object.getJalviewModelSequence();
2657
2658     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2659             : null;
2660
2661     // ////////////////////////////////
2662     // LOAD SEQUENCES
2663
2664     List<SequenceI> hiddenSeqs = null;
2665     jalview.datamodel.Sequence jseq;
2666
2667     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2668
2669     boolean multipleView = false;
2670     SequenceI referenceseqForView = null;
2671     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2672     int vi = 0; // counter in vamsasSeq array
2673     for (int i = 0; i < jseqs.length; i++)
2674     {
2675       String seqId = jseqs[i].getId();
2676
2677       if (seqRefIds.get(seqId) != null)
2678       {
2679         tmpseqs.add(seqRefIds.get(seqId));
2680         multipleView = true;
2681       }
2682       else
2683       {
2684         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2685                 vamsasSeq[vi].getSequence());
2686         jseq.setDescription(vamsasSeq[vi].getDescription());
2687         jseq.setStart(jseqs[i].getStart());
2688         jseq.setEnd(jseqs[i].getEnd());
2689         jseq.setVamsasId(uniqueSetSuffix + seqId);
2690         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
2691         tmpseqs.add(jseq);
2692         vi++;
2693       }
2694
2695       if (jseqs[i].hasViewreference() && jseqs[i].getViewreference())
2696       {
2697         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
2698       }
2699
2700       if (jseqs[i].getHidden())
2701       {
2702         if (hiddenSeqs == null)
2703         {
2704           hiddenSeqs = new ArrayList<SequenceI>();
2705         }
2706
2707         hiddenSeqs.add(seqRefIds.get(seqId));
2708       }
2709     }
2710
2711     // /
2712     // Create the alignment object from the sequence set
2713     // ///////////////////////////////
2714     SequenceI[] orderedSeqs = tmpseqs
2715             .toArray(new SequenceI[tmpseqs.size()]);
2716
2717     Alignment al = new Alignment(orderedSeqs);
2718
2719     if (referenceseqForView != null)
2720     {
2721       al.setSeqrep(referenceseqForView);
2722     }
2723     // / Add the alignment properties
2724     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2725     {
2726       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2727       al.setProperty(ssp.getKey(), ssp.getValue());
2728     }
2729
2730     // /
2731     // SequenceFeatures are added to the DatasetSequence,
2732     // so we must create or recover the dataset before loading features
2733     // ///////////////////////////////
2734     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2735     {
2736       // older jalview projects do not have a dataset id.
2737       al.setDataset(null);
2738     }
2739     else
2740     {
2741       // recover dataset - passing on flag indicating if this a 'viewless'
2742       // sequence set (a.k.a. a stored dataset for the project)
2743       recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
2744               .getViewportCount() == 0);
2745     }
2746     // ///////////////////////////////
2747
2748     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2749     if (!multipleView)
2750     {
2751       // load sequence features, database references and any associated PDB
2752       // structures for the alignment
2753       for (int i = 0; i < vamsasSeq.length; i++)
2754       {
2755         if (jseqs[i].getFeaturesCount() > 0)
2756         {
2757           Features[] features = jseqs[i].getFeatures();
2758           for (int f = 0; f < features.length; f++)
2759           {
2760             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2761                     features[f].getType(), features[f].getDescription(),
2762                     features[f].getStatus(), features[f].getBegin(),
2763                     features[f].getEnd(), features[f].getFeatureGroup());
2764
2765             sf.setScore(features[f].getScore());
2766             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2767             {
2768               OtherData keyValue = features[f].getOtherData(od);
2769               if (keyValue.getKey().startsWith("LINK"))
2770               {
2771                 sf.addLink(keyValue.getValue());
2772               }
2773               else
2774               {
2775                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2776               }
2777
2778             }
2779
2780             al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
2781           }
2782         }
2783         if (vamsasSeq[i].getDBRefCount() > 0)
2784         {
2785           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
2786         }
2787         if (jseqs[i].getPdbidsCount() > 0)
2788         {
2789           Pdbids[] ids = jseqs[i].getPdbids();
2790           for (int p = 0; p < ids.length; p++)
2791           {
2792             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2793             entry.setId(ids[p].getId());
2794             if (ids[p].getType() != null)
2795             {
2796               if (ids[p].getType().equalsIgnoreCase("PDB"))
2797               {
2798                 entry.setType(PDBEntry.Type.PDB);
2799               }
2800               else
2801               {
2802                 entry.setType(PDBEntry.Type.FILE);
2803               }
2804             }
2805             if (ids[p].getFile() != null)
2806             {
2807               if (!pdbloaded.containsKey(ids[p].getFile()))
2808               {
2809                 entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
2810               }
2811               else
2812               {
2813                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
2814               }
2815             }
2816             StructureSelectionManager.getStructureSelectionManager(
2817                     Desktop.instance).registerPDBEntry(entry);
2818             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
2819           }
2820         }
2821       }
2822     } // end !multipleview
2823
2824     // ///////////////////////////////
2825     // LOAD SEQUENCE MAPPINGS
2826
2827     if (vamsasSet.getAlcodonFrameCount() > 0)
2828     {
2829       // TODO Potentially this should only be done once for all views of an
2830       // alignment
2831       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
2832       for (int i = 0; i < alc.length; i++)
2833       {
2834         AlignedCodonFrame cf = new AlignedCodonFrame();
2835         if (alc[i].getAlcodMapCount() > 0)
2836         {
2837           AlcodMap[] maps = alc[i].getAlcodMap();
2838           for (int m = 0; m < maps.length; m++)
2839           {
2840             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
2841             // Load Mapping
2842             jalview.datamodel.Mapping mapping = null;
2843             // attach to dna sequence reference.
2844             if (maps[m].getMapping() != null)
2845             {
2846               mapping = addMapping(maps[m].getMapping());
2847             }
2848             if (dnaseq != null && mapping.getTo() != null)
2849             {
2850               cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
2851             }
2852             else
2853             {
2854               // defer to later
2855               frefedSequence.add(new Object[] { maps[m].getDnasq(), cf,
2856                   mapping });
2857             }
2858           }
2859         }
2860         al.addCodonFrame(cf);
2861       }
2862     }
2863
2864     // ////////////////////////////////
2865     // LOAD ANNOTATIONS
2866     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
2867
2868     /*
2869      * store any annotations which forward reference a group's ID
2870      */
2871     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
2872
2873     if (vamsasSet.getAnnotationCount() > 0)
2874     {
2875       Annotation[] an = vamsasSet.getAnnotation();
2876
2877       for (int i = 0; i < an.length; i++)
2878       {
2879         Annotation annotation = an[i];
2880
2881         /**
2882          * test if annotation is automatically calculated for this view only
2883          */
2884         boolean autoForView = false;
2885         if (annotation.getLabel().equals("Quality")
2886                 || annotation.getLabel().equals("Conservation")
2887                 || annotation.getLabel().equals("Consensus"))
2888         {
2889           // Kludge for pre 2.5 projects which lacked the autocalculated flag
2890           autoForView = true;
2891           if (!annotation.hasAutoCalculated())
2892           {
2893             annotation.setAutoCalculated(true);
2894           }
2895         }
2896         if (autoForView
2897                 || (annotation.hasAutoCalculated() && annotation
2898                         .isAutoCalculated()))
2899         {
2900           // remove ID - we don't recover annotation from other views for
2901           // view-specific annotation
2902           annotation.setId(null);
2903         }
2904
2905         // set visiblity for other annotation in this view
2906         String annotationId = annotation.getId();
2907         if (annotationId != null && annotationIds.containsKey(annotationId))
2908         {
2909           AlignmentAnnotation jda = annotationIds.get(annotationId);
2910           // in principle Visible should always be true for annotation displayed
2911           // in multiple views
2912           if (annotation.hasVisible())
2913           {
2914             jda.visible = annotation.getVisible();
2915           }
2916
2917           al.addAnnotation(jda);
2918
2919           continue;
2920         }
2921         // Construct new annotation from model.
2922         AnnotationElement[] ae = annotation.getAnnotationElement();
2923         jalview.datamodel.Annotation[] anot = null;
2924         java.awt.Color firstColour = null;
2925         int anpos;
2926         if (!annotation.getScoreOnly())
2927         {
2928           anot = new jalview.datamodel.Annotation[al.getWidth()];
2929           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
2930           {
2931             anpos = ae[aa].getPosition();
2932
2933             if (anpos >= anot.length)
2934             {
2935               continue;
2936             }
2937
2938             anot[anpos] = new jalview.datamodel.Annotation(
2939
2940             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
2941                     (ae[aa].getSecondaryStructure() == null || ae[aa]
2942                             .getSecondaryStructure().length() == 0) ? ' '
2943                             : ae[aa].getSecondaryStructure().charAt(0),
2944                     ae[aa].getValue()
2945
2946             );
2947             // JBPNote: Consider verifying dataflow for IO of secondary
2948             // structure annotation read from Stockholm files
2949             // this was added to try to ensure that
2950             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
2951             // {
2952             // anot[ae[aa].getPosition()].displayCharacter = "";
2953             // }
2954             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
2955             if (firstColour == null)
2956             {
2957               firstColour = anot[anpos].colour;
2958             }
2959           }
2960         }
2961         jalview.datamodel.AlignmentAnnotation jaa = null;
2962
2963         if (annotation.getGraph())
2964         {
2965           float llim = 0, hlim = 0;
2966           // if (autoForView || an[i].isAutoCalculated()) {
2967           // hlim=11f;
2968           // }
2969           jaa = new jalview.datamodel.AlignmentAnnotation(
2970                   annotation.getLabel(), annotation.getDescription(), anot,
2971                   llim, hlim, annotation.getGraphType());
2972
2973           jaa.graphGroup = annotation.getGraphGroup();
2974           jaa._linecolour = firstColour;
2975           if (annotation.getThresholdLine() != null)
2976           {
2977             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
2978                     .getThresholdLine().getValue(), annotation
2979                     .getThresholdLine().getLabel(), new java.awt.Color(
2980                     annotation.getThresholdLine().getColour())));
2981
2982           }
2983           if (autoForView || annotation.isAutoCalculated())
2984           {
2985             // Hardwire the symbol display line to ensure that labels for
2986             // histograms are displayed
2987             jaa.hasText = true;
2988           }
2989         }
2990         else
2991         {
2992           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
2993                   an[i].getDescription(), anot);
2994           jaa._linecolour = firstColour;
2995         }
2996         // register new annotation
2997         if (an[i].getId() != null)
2998         {
2999           annotationIds.put(an[i].getId(), jaa);
3000           jaa.annotationId = an[i].getId();
3001         }
3002         // recover sequence association
3003         String sequenceRef = an[i].getSequenceRef();
3004         if (sequenceRef != null)
3005         {
3006           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3007           SequenceI sequence = seqRefIds.get(sequenceRef);
3008           if (sequence == null)
3009           {
3010             // in pre-2.9 projects sequence ref is to sequence name
3011             sequence = al.findName(sequenceRef);
3012           }
3013           if (sequence != null)
3014           {
3015             jaa.createSequenceMapping(sequence, 1, true);
3016             sequence.addAlignmentAnnotation(jaa);
3017           }
3018         }
3019         // and make a note of any group association
3020         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3021         {
3022           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3023                   .get(an[i].getGroupRef());
3024           if (aal == null)
3025           {
3026             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
3027             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3028           }
3029           aal.add(jaa);
3030         }
3031
3032         if (an[i].hasScore())
3033         {
3034           jaa.setScore(an[i].getScore());
3035         }
3036         if (an[i].hasVisible())
3037         {
3038           jaa.visible = an[i].getVisible();
3039         }
3040
3041         if (an[i].hasCentreColLabels())
3042         {
3043           jaa.centreColLabels = an[i].getCentreColLabels();
3044         }
3045
3046         if (an[i].hasScaleColLabels())
3047         {
3048           jaa.scaleColLabel = an[i].getScaleColLabels();
3049         }
3050         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3051         {
3052           // newer files have an 'autoCalculated' flag and store calculation
3053           // state in viewport properties
3054           jaa.autoCalculated = true; // means annotation will be marked for
3055           // update at end of load.
3056         }
3057         if (an[i].hasGraphHeight())
3058         {
3059           jaa.graphHeight = an[i].getGraphHeight();
3060         }
3061         if (an[i].hasBelowAlignment())
3062         {
3063           jaa.belowAlignment = an[i].isBelowAlignment();
3064         }
3065         jaa.setCalcId(an[i].getCalcId());
3066         if (an[i].getPropertyCount() > 0)
3067         {
3068           for (jalview.schemabinding.version2.Property prop : an[i]
3069                   .getProperty())
3070           {
3071             jaa.setProperty(prop.getName(), prop.getValue());
3072           }
3073         }
3074         if (jaa.autoCalculated)
3075         {
3076           autoAlan.add(new JvAnnotRow(i, jaa));
3077         }
3078         else
3079         // if (!autoForView)
3080         {
3081           // add autocalculated group annotation and any user created annotation
3082           // for the view
3083           al.addAnnotation(jaa);
3084         }
3085       }
3086     }
3087     // ///////////////////////
3088     // LOAD GROUPS
3089     // Create alignment markup and styles for this view
3090     if (jms.getJGroupCount() > 0)
3091     {
3092       JGroup[] groups = jms.getJGroup();
3093       boolean addAnnotSchemeGroup = false;
3094       for (int i = 0; i < groups.length; i++)
3095       {
3096         JGroup jGroup = groups[i];
3097         ColourSchemeI cs = null;
3098         if (jGroup.getColour() != null)
3099         {
3100           if (jGroup.getColour().startsWith("ucs"))
3101           {
3102             cs = getUserColourScheme(jms, jGroup.getColour());
3103           }
3104           else if (jGroup.getColour().equals("AnnotationColourGradient")
3105                   && jGroup.getAnnotationColours() != null)
3106           {
3107             addAnnotSchemeGroup = true;
3108             cs = null;
3109           }
3110           else
3111           {
3112             cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
3113           }
3114
3115           if (cs != null)
3116           {
3117             cs.setThreshold(jGroup.getPidThreshold(), true);
3118           }
3119         }
3120
3121         Vector<SequenceI> seqs = new Vector<SequenceI>();
3122
3123         for (int s = 0; s < jGroup.getSeqCount(); s++)
3124         {
3125           String seqId = jGroup.getSeq(s) + "";
3126           SequenceI ts = seqRefIds.get(seqId);
3127
3128           if (ts != null)
3129           {
3130             seqs.addElement(ts);
3131           }
3132         }
3133
3134         if (seqs.size() < 1)
3135         {
3136           continue;
3137         }
3138
3139         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3140                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3141                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3142
3143         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3144
3145         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3146         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3147         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3148                 .isShowUnconserved() : false);
3149         sg.thresholdTextColour = jGroup.getTextColThreshold();
3150         if (jGroup.hasShowConsensusHistogram())
3151         {
3152           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3153         }
3154         ;
3155         if (jGroup.hasShowSequenceLogo())
3156         {
3157           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3158         }
3159         if (jGroup.hasNormaliseSequenceLogo())
3160         {
3161           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3162         }
3163         if (jGroup.hasIgnoreGapsinConsensus())
3164         {
3165           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3166         }
3167         if (jGroup.getConsThreshold() != 0)
3168         {
3169           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
3170                   "All", ResidueProperties.propHash, 3,
3171                   sg.getSequences(null), 0, sg.getWidth() - 1);
3172           c.calculate();
3173           c.verdict(false, 25);
3174           sg.cs.setConservation(c);
3175         }
3176
3177         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3178         {
3179           // re-instate unique group/annotation row reference
3180           List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
3181                   .getId());
3182           if (jaal != null)
3183           {
3184             for (AlignmentAnnotation jaa : jaal)
3185             {
3186               jaa.groupRef = sg;
3187               if (jaa.autoCalculated)
3188               {
3189                 // match up and try to set group autocalc alignment row for this
3190                 // annotation
3191                 if (jaa.label.startsWith("Consensus for "))
3192                 {
3193                   sg.setConsensus(jaa);
3194                 }
3195                 // match up and try to set group autocalc alignment row for this
3196                 // annotation
3197                 if (jaa.label.startsWith("Conservation for "))
3198                 {
3199                   sg.setConservationRow(jaa);
3200                 }
3201               }
3202             }
3203           }
3204         }
3205         al.addGroup(sg);
3206         if (addAnnotSchemeGroup)
3207         {
3208           // reconstruct the annotation colourscheme
3209           sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(),
3210                   null, al, jms, false);
3211         }
3212       }
3213     }
3214     if (view == null)
3215     {
3216       // only dataset in this model, so just return.
3217       return null;
3218     }
3219     // ///////////////////////////////
3220     // LOAD VIEWPORT
3221
3222     // If we just load in the same jar file again, the sequenceSetId
3223     // will be the same, and we end up with multiple references
3224     // to the same sequenceSet. We must modify this id on load
3225     // so that each load of the file gives a unique id
3226     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3227     String viewId = (view.getId() == null ? null : view.getId()
3228             + uniqueSetSuffix);
3229     AlignFrame af = null;
3230     AlignViewport av = null;
3231     // now check to see if we really need to create a new viewport.
3232     if (multipleView && viewportsAdded.size() == 0)
3233     {
3234       // We recovered an alignment for which a viewport already exists.
3235       // TODO: fix up any settings necessary for overlaying stored state onto
3236       // state recovered from another document. (may not be necessary).
3237       // we may need a binding from a viewport in memory to one recovered from
3238       // XML.
3239       // and then recover its containing af to allow the settings to be applied.
3240       // TODO: fix for vamsas demo
3241       System.err
3242               .println("About to recover a viewport for existing alignment: Sequence set ID is "
3243                       + uniqueSeqSetId);
3244       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3245       if (seqsetobj != null)
3246       {
3247         if (seqsetobj instanceof String)
3248         {
3249           uniqueSeqSetId = (String) seqsetobj;
3250           System.err
3251                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3252                           + uniqueSeqSetId);
3253         }
3254         else
3255         {
3256           System.err
3257                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
3258         }
3259
3260       }
3261     }
3262     /**
3263      * indicate that annotation colours are applied across all groups (pre
3264      * Jalview 2.8.1 behaviour)
3265      */
3266     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan(
3267             "2.8.1", object.getVersion());
3268
3269     AlignmentPanel ap = null;
3270     boolean isnewview = true;
3271     if (viewId != null)
3272     {
3273       // Check to see if this alignment already has a view id == viewId
3274       jalview.gui.AlignmentPanel views[] = Desktop
3275               .getAlignmentPanels(uniqueSeqSetId);
3276       if (views != null && views.length > 0)
3277       {
3278         for (int v = 0; v < views.length; v++)
3279         {
3280           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3281           {
3282             // recover the existing alignpanel, alignframe, viewport
3283             af = views[v].alignFrame;
3284             av = views[v].av;
3285             ap = views[v];
3286             // TODO: could even skip resetting view settings if we don't want to
3287             // change the local settings from other jalview processes
3288             isnewview = false;
3289           }
3290         }
3291       }
3292     }
3293
3294     if (isnewview)
3295     {
3296       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3297               uniqueSeqSetId, viewId, autoAlan);
3298       av = af.viewport;
3299       ap = af.alignPanel;
3300     }
3301
3302     /*
3303      * Load any trees, PDB structures and viewers
3304      * 
3305      * Not done if flag is false (when this method is used for New View)
3306      */
3307     if (loadTreesAndStructures)
3308     {
3309       loadTrees(jms, view, af, av, ap);
3310       loadPDBStructures(jprovider, jseqs, af, ap);
3311       loadRnaViewers(jprovider, jseqs, ap);
3312     }
3313     // and finally return.
3314     return af;
3315   }
3316
3317   /**
3318    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3319    * panel is restored from separate jar entries, two (gapped and trimmed) per
3320    * sequence and secondary structure.
3321    * 
3322    * Currently each viewer shows just one sequence and structure (gapped and
3323    * trimmed), however this method is designed to support multiple sequences or
3324    * structures in viewers if wanted in future.
3325    * 
3326    * @param jprovider
3327    * @param jseqs
3328    * @param ap
3329    */
3330   private void loadRnaViewers(jarInputStreamProvider jprovider,
3331           JSeq[] jseqs, AlignmentPanel ap)
3332   {
3333     /*
3334      * scan the sequences for references to viewers; create each one the first
3335      * time it is referenced, add Rna models to existing viewers
3336      */
3337     for (JSeq jseq : jseqs)
3338     {
3339       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3340       {
3341         RnaViewer viewer = jseq.getRnaViewer(i);
3342         AppVarna appVarna = findOrCreateVarnaViewer(viewer,
3343                 uniqueSetSuffix, ap);
3344
3345         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3346         {
3347           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3348           SequenceI seq = seqRefIds.get(jseq.getId());
3349           AlignmentAnnotation ann = this.annotationIds.get(ss
3350                   .getAnnotationId());
3351
3352           /*
3353            * add the structure to the Varna display (with session state copied
3354            * from the jar to a temporary file)
3355            */
3356           boolean gapped = ss.isGapped();
3357           String rnaTitle = ss.getTitle();
3358           String sessionState = ss.getViewerState();
3359           String tempStateFile = copyJarEntry(jprovider, sessionState,
3360                   "varna");
3361           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3362           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3363         }
3364         appVarna.setInitialSelection(viewer.getSelectedRna());
3365       }
3366     }
3367   }
3368
3369   /**
3370    * Locate and return an already instantiated matching AppVarna, or create one
3371    * if not found
3372    * 
3373    * @param viewer
3374    * @param viewIdSuffix
3375    * @param ap
3376    * @return
3377    */
3378   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3379           String viewIdSuffix, AlignmentPanel ap)
3380   {
3381     /*
3382      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3383      * if load is repeated
3384      */
3385     String postLoadId = viewer.getViewId() + viewIdSuffix;
3386     for (JInternalFrame frame : getAllFrames())
3387     {
3388       if (frame instanceof AppVarna)
3389       {
3390         AppVarna varna = (AppVarna) frame;
3391         if (postLoadId.equals(varna.getViewId()))
3392         {
3393           // this viewer is already instantiated
3394           // could in future here add ap as another 'parent' of the
3395           // AppVarna window; currently just 1-to-many
3396           return varna;
3397         }
3398       }
3399     }
3400
3401     /*
3402      * viewer not found - make it
3403      */
3404     RnaViewerModel model = new RnaViewerModel(postLoadId,
3405             viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
3406             viewer.getWidth(), viewer.getHeight(),
3407             viewer.getDividerLocation());
3408     AppVarna varna = new AppVarna(model, ap);
3409
3410     return varna;
3411   }
3412
3413   /**
3414    * Load any saved trees
3415    * 
3416    * @param jms
3417    * @param view
3418    * @param af
3419    * @param av
3420    * @param ap
3421    */
3422   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3423           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3424   {
3425     // TODO result of automated refactoring - are all these parameters needed?
3426     try
3427     {
3428       for (int t = 0; t < jms.getTreeCount(); t++)
3429       {
3430
3431         Tree tree = jms.getTree(t);
3432
3433         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3434         if (tp == null)
3435         {
3436           tp = af.ShowNewickTree(
3437                   new jalview.io.NewickFile(tree.getNewick()),
3438                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3439                   tree.getXpos(), tree.getYpos());
3440           if (tree.getId() != null)
3441           {
3442             // perhaps bind the tree id to something ?
3443           }
3444         }
3445         else
3446         {
3447           // update local tree attributes ?
3448           // TODO: should check if tp has been manipulated by user - if so its
3449           // settings shouldn't be modified
3450           tp.setTitle(tree.getTitle());
3451           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3452                   .getWidth(), tree.getHeight()));
3453           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3454           // views'
3455           // works still
3456           tp.treeCanvas.av = av; // af.viewport;
3457           tp.treeCanvas.ap = ap; // af.alignPanel;
3458
3459         }
3460         if (tp == null)
3461         {
3462           warn("There was a problem recovering stored Newick tree: \n"
3463                   + tree.getNewick());
3464           continue;
3465         }
3466
3467         tp.fitToWindow.setState(tree.getFitToWindow());
3468         tp.fitToWindow_actionPerformed(null);
3469
3470         if (tree.getFontName() != null)
3471         {
3472           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3473                   .getFontStyle(), tree.getFontSize()));
3474         }
3475         else
3476         {
3477           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3478                   .getFontStyle(), tree.getFontSize()));
3479         }
3480
3481         tp.showPlaceholders(tree.getMarkUnlinked());
3482         tp.showBootstrap(tree.getShowBootstrap());
3483         tp.showDistances(tree.getShowDistances());
3484
3485         tp.treeCanvas.threshold = tree.getThreshold();
3486
3487         if (tree.getCurrentTree())
3488         {
3489           af.viewport.setCurrentTree(tp.getTree());
3490         }
3491       }
3492
3493     } catch (Exception ex)
3494     {
3495       ex.printStackTrace();
3496     }
3497   }
3498
3499   /**
3500    * Load and link any saved structure viewers.
3501    * 
3502    * @param jprovider
3503    * @param jseqs
3504    * @param af
3505    * @param ap
3506    */
3507   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3508           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3509   {
3510     /*
3511      * Run through all PDB ids on the alignment, and collect mappings between
3512      * distinct view ids and all sequences referring to that view.
3513      */
3514     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3515
3516     for (int i = 0; i < jseqs.length; i++)
3517     {
3518       if (jseqs[i].getPdbidsCount() > 0)
3519       {
3520         Pdbids[] ids = jseqs[i].getPdbids();
3521         for (int p = 0; p < ids.length; p++)
3522         {
3523           final int structureStateCount = ids[p].getStructureStateCount();
3524           for (int s = 0; s < structureStateCount; s++)
3525           {
3526             // check to see if we haven't already created this structure view
3527             final StructureState structureState = ids[p]
3528                     .getStructureState(s);
3529             String sviewid = (structureState.getViewId() == null) ? null
3530                     : structureState.getViewId() + uniqueSetSuffix;
3531             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3532             // Originally : ids[p].getFile()
3533             // : TODO: verify external PDB file recovery still works in normal
3534             // jalview project load
3535             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
3536             jpdb.setId(ids[p].getId());
3537
3538             int x = structureState.getXpos();
3539             int y = structureState.getYpos();
3540             int width = structureState.getWidth();
3541             int height = structureState.getHeight();
3542
3543             // Probably don't need to do this anymore...
3544             // Desktop.desktop.getComponentAt(x, y);
3545             // TODO: NOW: check that this recovers the PDB file correctly.
3546             String pdbFile = loadPDBFile(jprovider, ids[p].getId());
3547             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3548                     .getId() + "");
3549             if (sviewid == null)
3550             {
3551               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3552                       + "," + height;
3553             }
3554             if (!structureViewers.containsKey(sviewid))
3555             {
3556               structureViewers.put(sviewid,
3557                       new StructureViewerModel(x, y, width, height, false,
3558                               false, true, structureState.getViewId(),
3559                               structureState.getType()));
3560               // Legacy pre-2.7 conversion JAL-823 :
3561               // do not assume any view has to be linked for colour by
3562               // sequence
3563             }
3564
3565             // assemble String[] { pdb files }, String[] { id for each
3566             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3567             // seqs_file 2}, boolean[] {
3568             // linkAlignPanel,superposeWithAlignpanel}} from hash
3569             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3570             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3571                     | (structureState.hasAlignwithAlignPanel() ? structureState
3572                             .getAlignwithAlignPanel() : false));
3573
3574             /*
3575              * Default colour by linked panel to false if not specified (e.g.
3576              * for pre-2.7 projects)
3577              */
3578             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3579             colourWithAlignPanel |= (structureState
3580                     .hasColourwithAlignPanel() ? structureState
3581                     .getColourwithAlignPanel() : false);
3582             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3583
3584             /*
3585              * Default colour by viewer to true if not specified (e.g. for
3586              * pre-2.7 projects)
3587              */
3588             boolean colourByViewer = jmoldat.isColourByViewer();
3589             colourByViewer &= structureState.hasColourByJmol() ? structureState
3590                     .getColourByJmol() : true;
3591             jmoldat.setColourByViewer(colourByViewer);
3592
3593             if (jmoldat.getStateData().length() < structureState
3594                     .getContent().length())
3595             {
3596               {
3597                 jmoldat.setStateData(structureState.getContent());
3598               }
3599             }
3600             if (ids[p].getFile() != null)
3601             {
3602               File mapkey = new File(ids[p].getFile());
3603               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3604               if (seqstrmaps == null)
3605               {
3606                 jmoldat.getFileData().put(
3607                         mapkey,
3608                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3609                                 ids[p].getId()));
3610               }
3611               if (!seqstrmaps.getSeqList().contains(seq))
3612               {
3613                 seqstrmaps.getSeqList().add(seq);
3614                 // TODO and chains?
3615               }
3616             }
3617             else
3618             {
3619               errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
3620               warn(errorMessage);
3621             }
3622           }
3623         }
3624       }
3625     }
3626     // Instantiate the associated structure views
3627     for (Entry<String, StructureViewerModel> entry : structureViewers
3628             .entrySet())
3629     {
3630       try
3631       {
3632         createOrLinkStructureViewer(entry, af, ap, jprovider);
3633       } catch (Exception e)
3634       {
3635         System.err.println("Error loading structure viewer: "
3636                 + e.getMessage());
3637         // failed - try the next one
3638       }
3639     }
3640   }
3641
3642   /**
3643    * 
3644    * @param viewerData
3645    * @param af
3646    * @param ap
3647    * @param jprovider
3648    */
3649   protected void createOrLinkStructureViewer(
3650           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3651           AlignmentPanel ap, jarInputStreamProvider jprovider)
3652   {
3653     final StructureViewerModel stateData = viewerData.getValue();
3654
3655     /*
3656      * Search for any viewer windows already open from other alignment views
3657      * that exactly match the stored structure state
3658      */
3659     StructureViewerBase comp = findMatchingViewer(viewerData);
3660
3661     if (comp != null)
3662     {
3663       linkStructureViewer(ap, comp, stateData);
3664       return;
3665     }
3666
3667     /*
3668      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3669      * "viewer_"+stateData.viewId
3670      */
3671     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3672     {
3673       createChimeraViewer(viewerData, af, jprovider);
3674     }
3675     else
3676     {
3677       /*
3678        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3679        */
3680       createJmolViewer(viewerData, af, jprovider);
3681     }
3682   }
3683
3684   /**
3685    * Create a new Chimera viewer.
3686    * 
3687    * @param data
3688    * @param af
3689    * @param jprovider
3690    */
3691   protected void createChimeraViewer(
3692           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3693           jarInputStreamProvider jprovider)
3694   {
3695     StructureViewerModel data = viewerData.getValue();
3696     String chimeraSessionFile = data.getStateData();
3697
3698     /*
3699      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3700      * 
3701      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3702      * 'uniquified' sviewid used to reconstruct the viewer here
3703      */
3704     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3705     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3706             "chimera");
3707
3708     Set<Entry<File, StructureData>> fileData = data.getFileData()
3709             .entrySet();
3710     List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3711     List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3712     for (Entry<File, StructureData> pdb : fileData)
3713     {
3714       String filePath = pdb.getValue().getFilePath();
3715       String pdbId = pdb.getValue().getPdbId();
3716       // pdbs.add(new PDBEntry(filePath, pdbId));
3717       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3718       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3719       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3720       allseqs.add(seqs);
3721     }
3722
3723     boolean colourByChimera = data.isColourByViewer();
3724     boolean colourBySequence = data.isColourWithAlignPanel();
3725
3726     // TODO use StructureViewer as a factory here, see JAL-1761
3727     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3728     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3729             .size()][]);
3730     String newViewId = viewerData.getKey();
3731
3732     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3733             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3734             colourBySequence, newViewId);
3735     cvf.setSize(data.getWidth(), data.getHeight());
3736     cvf.setLocation(data.getX(), data.getY());
3737   }
3738
3739   /**
3740    * Create a new Jmol window. First parse the Jmol state to translate filenames
3741    * loaded into the view, and record the order in which files are shown in the
3742    * Jmol view, so we can add the sequence mappings in same order.
3743    * 
3744    * @param viewerData
3745    * @param af
3746    * @param jprovider
3747    */
3748   protected void createJmolViewer(
3749           final Entry<String, StructureViewerModel> viewerData,
3750           AlignFrame af, jarInputStreamProvider jprovider)
3751   {
3752     final StructureViewerModel svattrib = viewerData.getValue();
3753     String state = svattrib.getStateData();
3754
3755     /*
3756      * Pre-2.9: state element value is the Jmol state string
3757      * 
3758      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3759      * + viewId
3760      */
3761     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3762     {
3763       state = readJarEntry(jprovider,
3764               getViewerJarEntryName(svattrib.getViewId()));
3765     }
3766
3767     List<String> pdbfilenames = new ArrayList<String>();
3768     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3769     List<String> pdbids = new ArrayList<String>();
3770     StringBuilder newFileLoc = new StringBuilder(64);
3771     int cp = 0, ncp, ecp;
3772     Map<File, StructureData> oldFiles = svattrib.getFileData();
3773     while ((ncp = state.indexOf("load ", cp)) > -1)
3774     {
3775       do
3776       {
3777         // look for next filename in load statement
3778         newFileLoc.append(state.substring(cp,
3779                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3780         String oldfilenam = state.substring(ncp,
3781                 ecp = state.indexOf("\"", ncp));
3782         // recover the new mapping data for this old filename
3783         // have to normalize filename - since Jmol and jalview do
3784         // filename
3785         // translation differently.
3786         StructureData filedat = oldFiles.get(new File(oldfilenam));
3787         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3788         pdbfilenames.add(filedat.getFilePath());
3789         pdbids.add(filedat.getPdbId());
3790         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3791         newFileLoc.append("\"");
3792         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3793                       // look for next file statement.
3794       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3795     }
3796     if (cp > 0)
3797     {
3798       // just append rest of state
3799       newFileLoc.append(state.substring(cp));
3800     }
3801     else
3802     {
3803       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3804       newFileLoc = new StringBuilder(state);
3805       newFileLoc.append("; load append ");
3806       for (File id : oldFiles.keySet())
3807       {
3808         // add this and any other pdb files that should be present in
3809         // the viewer
3810         StructureData filedat = oldFiles.get(id);
3811         newFileLoc.append(filedat.getFilePath());
3812         pdbfilenames.add(filedat.getFilePath());
3813         pdbids.add(filedat.getPdbId());
3814         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3815         newFileLoc.append(" \"");
3816         newFileLoc.append(filedat.getFilePath());
3817         newFileLoc.append("\"");
3818
3819       }
3820       newFileLoc.append(";");
3821     }
3822
3823     if (newFileLoc.length() == 0)
3824     {
3825       return;
3826     }
3827     int histbug = newFileLoc.indexOf("history = ");
3828     if (histbug > -1)
3829     {
3830       /*
3831        * change "history = [true|false];" to "history = [1|0];"
3832        */
3833       histbug += 10;
3834       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3835       String val = (diff == -1) ? null : newFileLoc
3836               .substring(histbug, diff);
3837       if (val != null && val.length() >= 4)
3838       {
3839         if (val.contains("e")) // eh? what can it be?
3840         {
3841           if (val.trim().equals("true"))
3842           {
3843             val = "1";
3844           }
3845           else
3846           {
3847             val = "0";
3848           }
3849           newFileLoc.replace(histbug, diff, val);
3850         }
3851       }
3852     }
3853
3854     final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
3855             .size()]);
3856     final String[] id = pdbids.toArray(new String[pdbids.size()]);
3857     final SequenceI[][] sq = seqmaps
3858             .toArray(new SequenceI[seqmaps.size()][]);
3859     final String fileloc = newFileLoc.toString();
3860     final String sviewid = viewerData.getKey();
3861     final AlignFrame alf = af;
3862     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
3863             svattrib.getWidth(), svattrib.getHeight());
3864     try
3865     {
3866       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
3867       {
3868         @Override
3869         public void run()
3870         {
3871           JalviewStructureDisplayI sview = null;
3872           try
3873           {
3874             sview = new StructureViewer(alf.alignPanel
3875                     .getStructureSelectionManager()).createView(
3876                     StructureViewer.ViewerType.JMOL, pdbf, id, sq,
3877                     alf.alignPanel, svattrib, fileloc, rect, sviewid);
3878             addNewStructureViewer(sview);
3879           } catch (OutOfMemoryError ex)
3880           {
3881             new OOMWarning("restoring structure view for PDB id " + id,
3882                     (OutOfMemoryError) ex.getCause());
3883             if (sview != null && sview.isVisible())
3884             {
3885               sview.closeViewer(false);
3886               sview.setVisible(false);
3887               sview.dispose();
3888             }
3889           }
3890         }
3891       });
3892     } catch (InvocationTargetException ex)
3893     {
3894       warn("Unexpected error when opening Jmol view.", ex);
3895
3896     } catch (InterruptedException e)
3897     {
3898       // e.printStackTrace();
3899     }
3900
3901   }
3902
3903   /**
3904    * Generates a name for the entry in the project jar file to hold state
3905    * information for a structure viewer
3906    * 
3907    * @param viewId
3908    * @return
3909    */
3910   protected String getViewerJarEntryName(String viewId)
3911   {
3912     return VIEWER_PREFIX + viewId;
3913   }
3914
3915   /**
3916    * Returns any open frame that matches given structure viewer data. The match
3917    * is based on the unique viewId, or (for older project versions) the frame's
3918    * geometry.
3919    * 
3920    * @param viewerData
3921    * @return
3922    */
3923   protected StructureViewerBase findMatchingViewer(
3924           Entry<String, StructureViewerModel> viewerData)
3925   {
3926     final String sviewid = viewerData.getKey();
3927     final StructureViewerModel svattrib = viewerData.getValue();
3928     StructureViewerBase comp = null;
3929     JInternalFrame[] frames = getAllFrames();
3930     for (JInternalFrame frame : frames)
3931     {
3932       if (frame instanceof StructureViewerBase)
3933       {
3934         /*
3935          * Post jalview 2.4 schema includes structure view id
3936          */
3937         if (sviewid != null
3938                 && ((StructureViewerBase) frame).getViewId()
3939                         .equals(sviewid))
3940         {
3941           comp = (StructureViewerBase) frame;
3942           break; // break added in 2.9
3943         }
3944         /*
3945          * Otherwise test for matching position and size of viewer frame
3946          */
3947         else if (frame.getX() == svattrib.getX()
3948                 && frame.getY() == svattrib.getY()
3949                 && frame.getHeight() == svattrib.getHeight()
3950                 && frame.getWidth() == svattrib.getWidth())
3951         {
3952           comp = (StructureViewerBase) frame;
3953           // no break in faint hope of an exact match on viewId
3954         }
3955       }
3956     }
3957     return comp;
3958   }
3959
3960   /**
3961    * Link an AlignmentPanel to an existing structure viewer.
3962    * 
3963    * @param ap
3964    * @param viewer
3965    * @param oldFiles
3966    * @param useinViewerSuperpos
3967    * @param usetoColourbyseq
3968    * @param viewerColouring
3969    */
3970   protected void linkStructureViewer(AlignmentPanel ap,
3971           StructureViewerBase viewer, StructureViewerModel stateData)
3972   {
3973     // NOTE: if the jalview project is part of a shared session then
3974     // view synchronization should/could be done here.
3975
3976     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
3977     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
3978     final boolean viewerColouring = stateData.isColourByViewer();
3979     Map<File, StructureData> oldFiles = stateData.getFileData();
3980
3981     /*
3982      * Add mapping for sequences in this view to an already open viewer
3983      */
3984     final AAStructureBindingModel binding = viewer.getBinding();
3985     for (File id : oldFiles.keySet())
3986     {
3987       // add this and any other pdb files that should be present in the
3988       // viewer
3989       StructureData filedat = oldFiles.get(id);
3990       String pdbFile = filedat.getFilePath();
3991       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
3992       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE);
3993       binding.addSequenceForStructFile(pdbFile, seq);
3994     }
3995     // and add the AlignmentPanel's reference to the view panel
3996     viewer.addAlignmentPanel(ap);
3997     if (useinViewerSuperpos)
3998     {
3999       viewer.useAlignmentPanelForSuperposition(ap);
4000     }
4001     else
4002     {
4003       viewer.excludeAlignmentPanelForSuperposition(ap);
4004     }
4005     if (usetoColourbyseq)
4006     {
4007       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4008     }
4009     else
4010     {
4011       viewer.excludeAlignmentPanelForColourbyseq(ap);
4012     }
4013   }
4014
4015   /**
4016    * Get all frames within the Desktop.
4017    * 
4018    * @return
4019    */
4020   protected JInternalFrame[] getAllFrames()
4021   {
4022     JInternalFrame[] frames = null;
4023     // TODO is this necessary - is it safe - risk of hanging?
4024     do
4025     {
4026       try
4027       {
4028         frames = Desktop.desktop.getAllFrames();
4029       } catch (ArrayIndexOutOfBoundsException e)
4030       {
4031         // occasional No such child exceptions are thrown here...
4032         try
4033         {
4034           Thread.sleep(10);
4035         } catch (InterruptedException f)
4036         {
4037         }
4038       }
4039     } while (frames == null);
4040     return frames;
4041   }
4042
4043   /**
4044    * Answers true if 'version' is equal to or later than 'supported', where each
4045    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4046    * changes. Development and test values for 'version' are leniently treated
4047    * i.e. answer true.
4048    * 
4049    * @param supported
4050    *          - minimum version we are comparing against
4051    * @param version
4052    *          - version of data being processsed
4053    * @return
4054    */
4055   public static boolean isVersionStringLaterThan(String supported,
4056           String version)
4057   {
4058     if (supported == null || version == null
4059             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4060             || version.equalsIgnoreCase("Test")
4061             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4062     {
4063       System.err.println("Assuming project file with "
4064               + (version == null ? "null" : version)
4065               + " is compatible with Jalview version " + supported);
4066       return true;
4067     }
4068     else
4069     {
4070       return StringUtils.compareVersions(version, supported, "b") >= 0;
4071     }
4072   }
4073
4074   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4075
4076   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4077   {
4078     if (newStructureViewers != null)
4079     {
4080       sview.getBinding().setFinishedLoadingFromArchive(false);
4081       newStructureViewers.add(sview);
4082     }
4083   }
4084
4085   protected void setLoadingFinishedForNewStructureViewers()
4086   {
4087     if (newStructureViewers != null)
4088     {
4089       for (JalviewStructureDisplayI sview : newStructureViewers)
4090       {
4091         sview.getBinding().setFinishedLoadingFromArchive(true);
4092       }
4093       newStructureViewers.clear();
4094       newStructureViewers = null;
4095     }
4096   }
4097
4098   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4099           List<SequenceI> hiddenSeqs, Alignment al,
4100           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4101           String viewId, List<JvAnnotRow> autoAlan)
4102   {
4103     AlignFrame af = null;
4104     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4105             uniqueSeqSetId, viewId);
4106
4107     af.setFileName(file, "Jalview");
4108
4109     for (int i = 0; i < JSEQ.length; i++)
4110     {
4111       af.viewport.setSequenceColour(af.viewport.getAlignment()
4112               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4113     }
4114
4115     if (al.hasSeqrep())
4116     {
4117       af.getViewport().setColourByReferenceSeq(true);
4118       af.getViewport().setDisplayReferenceSeq(true);
4119     }
4120
4121     af.viewport.setGatherViewsHere(view.getGatheredViews());
4122
4123     if (view.getSequenceSetId() != null)
4124     {
4125       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4126
4127       af.viewport.setSequenceSetId(uniqueSeqSetId);
4128       if (av != null)
4129       {
4130         // propagate shared settings to this new view
4131         af.viewport.setHistoryList(av.getHistoryList());
4132         af.viewport.setRedoList(av.getRedoList());
4133       }
4134       else
4135       {
4136         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4137       }
4138       // TODO: check if this method can be called repeatedly without
4139       // side-effects if alignpanel already registered.
4140       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4141     }
4142     // apply Hidden regions to view.
4143     if (hiddenSeqs != null)
4144     {
4145       for (int s = 0; s < JSEQ.length; s++)
4146       {
4147         SequenceGroup hidden = new SequenceGroup();
4148         boolean isRepresentative = false;
4149         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4150         {
4151           isRepresentative = true;
4152           SequenceI sequenceToHide = al.getSequenceAt(JSEQ[s]
4153                   .getHiddenSequences(r));
4154           hidden.addSequence(sequenceToHide, false);
4155           // remove from hiddenSeqs list so we don't try to hide it twice
4156           hiddenSeqs.remove(sequenceToHide);
4157         }
4158         if (isRepresentative)
4159         {
4160           SequenceI representativeSequence = al.getSequenceAt(s);
4161           hidden.addSequence(representativeSequence, false);
4162           af.viewport.hideRepSequences(representativeSequence, hidden);
4163         }
4164       }
4165
4166       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4167               .size()]);
4168       af.viewport.hideSequence(hseqs);
4169
4170     }
4171     // recover view properties and display parameters
4172     if (view.getViewName() != null)
4173     {
4174       af.viewport.viewName = view.getViewName();
4175       af.setInitialTabVisible();
4176     }
4177     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4178             view.getHeight());
4179
4180     af.viewport.setShowAnnotation(view.getShowAnnotation());
4181     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4182
4183     af.viewport.setColourText(view.getShowColourText());
4184
4185     af.viewport.setConservationSelected(view.getConservationSelected());
4186     af.viewport.setShowJVSuffix(view.getShowFullId());
4187     af.viewport.setRightAlignIds(view.getRightAlignIds());
4188     af.viewport.setFont(
4189             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4190                     .getFontSize()), true);
4191     ViewStyleI vs = af.viewport.getViewStyle();
4192     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4193     af.viewport.setViewStyle(vs);
4194     // TODO: allow custom charWidth/Heights to be restored by updating them
4195     // after setting font - which means set above to false
4196     af.viewport.setRenderGaps(view.getRenderGaps());
4197     af.viewport.setWrapAlignment(view.getWrapAlignment());
4198     af.viewport.setShowAnnotation(view.getShowAnnotation());
4199
4200     af.viewport.setShowBoxes(view.getShowBoxes());
4201
4202     af.viewport.setShowText(view.getShowText());
4203
4204     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4205     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4206     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4207     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4208             .isShowUnconserved() : false);
4209     af.viewport.setStartRes(view.getStartRes());
4210     af.viewport.setStartSeq(view.getStartSeq());
4211     af.alignPanel.updateLayout();
4212     ColourSchemeI cs = null;
4213     // apply colourschemes
4214     if (view.getBgColour() != null)
4215     {
4216       if (view.getBgColour().startsWith("ucs"))
4217       {
4218         cs = getUserColourScheme(jms, view.getBgColour());
4219       }
4220       else if (view.getBgColour().startsWith("Annotation"))
4221       {
4222         AnnotationColours viewAnnColour = view.getAnnotationColours();
4223         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4224
4225         // annpos
4226
4227       }
4228       else
4229       {
4230         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4231       }
4232
4233       if (cs != null)
4234       {
4235         cs.setThreshold(view.getPidThreshold(), true);
4236         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4237       }
4238     }
4239
4240     af.viewport.setGlobalColourScheme(cs);
4241     af.viewport.setColourAppliesToAllGroups(false);
4242
4243     if (view.getConservationSelected() && cs != null)
4244     {
4245       cs.setConservationInc(view.getConsThreshold());
4246     }
4247
4248     af.changeColour(cs);
4249
4250     af.viewport.setColourAppliesToAllGroups(true);
4251
4252     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4253
4254     if (view.hasCentreColumnLabels())
4255     {
4256       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4257     }
4258     if (view.hasIgnoreGapsinConsensus())
4259     {
4260       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4261               null);
4262     }
4263     if (view.hasFollowHighlight())
4264     {
4265       af.viewport.setFollowHighlight(view.getFollowHighlight());
4266     }
4267     if (view.hasFollowSelection())
4268     {
4269       af.viewport.followSelection = view.getFollowSelection();
4270     }
4271     if (view.hasShowConsensusHistogram())
4272     {
4273       af.viewport.setShowConsensusHistogram(view
4274               .getShowConsensusHistogram());
4275     }
4276     else
4277     {
4278       af.viewport.setShowConsensusHistogram(true);
4279     }
4280     if (view.hasShowSequenceLogo())
4281     {
4282       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4283     }
4284     else
4285     {
4286       af.viewport.setShowSequenceLogo(false);
4287     }
4288     if (view.hasNormaliseSequenceLogo())
4289     {
4290       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4291     }
4292     if (view.hasShowDbRefTooltip())
4293     {
4294       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4295     }
4296     if (view.hasShowNPfeatureTooltip())
4297     {
4298       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4299     }
4300     if (view.hasShowGroupConsensus())
4301     {
4302       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4303     }
4304     else
4305     {
4306       af.viewport.setShowGroupConsensus(false);
4307     }
4308     if (view.hasShowGroupConservation())
4309     {
4310       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4311     }
4312     else
4313     {
4314       af.viewport.setShowGroupConservation(false);
4315     }
4316
4317     // recover featre settings
4318     if (jms.getFeatureSettings() != null)
4319     {
4320       FeaturesDisplayed fdi;
4321       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4322       String[] renderOrder = new String[jms.getFeatureSettings()
4323               .getSettingCount()];
4324       Map<String, FeatureColourI> featureColours = new Hashtable<String, FeatureColourI>();
4325       Map<String, Float> featureOrder = new Hashtable<String, Float>();
4326
4327       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4328       {
4329         Setting setting = jms.getFeatureSettings().getSetting(fs);
4330         if (setting.hasMincolour())
4331         {
4332           FeatureColourI gc = setting.hasMin() ? new FeatureColour(
4333                   new Color(setting.getMincolour()), new Color(
4334                           setting.getColour()), setting.getMin(),
4335                   setting.getMax()) : new FeatureColour(new Color(
4336                   setting.getMincolour()), new Color(setting.getColour()),
4337                   0, 1);
4338           if (setting.hasThreshold())
4339           {
4340             gc.setThreshold(setting.getThreshold());
4341             int threshstate = setting.getThreshstate();
4342             // -1 = None, 0 = Below, 1 = Above threshold
4343             if (threshstate == 0)
4344             {
4345               gc.setBelowThreshold(true);
4346             }
4347             else if (threshstate == 1)
4348             {
4349               gc.setAboveThreshold(true);
4350             }
4351           }
4352           gc.setAutoScaled(true); // default
4353           if (setting.hasAutoScale())
4354           {
4355             gc.setAutoScaled(setting.getAutoScale());
4356           }
4357           if (setting.hasColourByLabel())
4358           {
4359             gc.setColourByLabel(setting.getColourByLabel());
4360           }
4361           // and put in the feature colour table.
4362           featureColours.put(setting.getType(), gc);
4363         }
4364         else
4365         {
4366           featureColours.put(setting.getType(), new FeatureColour(
4367                   new Color(setting.getColour())));
4368         }
4369         renderOrder[fs] = setting.getType();
4370         if (setting.hasOrder())
4371         {
4372           featureOrder.put(setting.getType(), setting.getOrder());
4373         }
4374         else
4375         {
4376           featureOrder.put(setting.getType(), new Float(fs
4377                   / jms.getFeatureSettings().getSettingCount()));
4378         }
4379         if (setting.getDisplay())
4380         {
4381           fdi.setVisible(setting.getType());
4382         }
4383       }
4384       Map<String, Boolean> fgtable = new Hashtable<String, Boolean>();
4385       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4386       {
4387         Group grp = jms.getFeatureSettings().getGroup(gs);
4388         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4389       }
4390       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4391       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4392       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4393       FeatureRendererSettings frs = new FeatureRendererSettings(
4394               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4395       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4396               .transferSettings(frs);
4397
4398     }
4399
4400     if (view.getHiddenColumnsCount() > 0)
4401     {
4402       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4403       {
4404         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4405                 .getHiddenColumns(c).getEnd() // +1
4406                 );
4407       }
4408     }
4409     if (view.getCalcIdParam() != null)
4410     {
4411       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4412       {
4413         if (calcIdParam != null)
4414         {
4415           if (recoverCalcIdParam(calcIdParam, af.viewport))
4416           {
4417           }
4418           else
4419           {
4420             warn("Couldn't recover parameters for "
4421                     + calcIdParam.getCalcId());
4422           }
4423         }
4424       }
4425     }
4426     af.setMenusFromViewport(af.viewport);
4427
4428     // TODO: we don't need to do this if the viewport is aready visible.
4429     /*
4430      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4431      * has a 'cdna/protein complement' view, in which case save it in order to
4432      * populate a SplitFrame once all views have been read in.
4433      */
4434     String complementaryViewId = view.getComplementId();
4435     if (complementaryViewId == null)
4436     {
4437       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4438               view.getHeight());
4439       // recompute any autoannotation
4440       af.alignPanel.updateAnnotation(false, true);
4441       reorderAutoannotation(af, al, autoAlan);
4442       af.alignPanel.alignmentChanged();
4443     }
4444     else
4445     {
4446       splitFrameCandidates.put(view, af);
4447     }
4448     return af;
4449   }
4450
4451   private ColourSchemeI constructAnnotationColour(
4452           AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
4453           JalviewModelSequence jms, boolean checkGroupAnnColour)
4454   {
4455     boolean propagateAnnColour = false;
4456     ColourSchemeI cs = null;
4457     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4458     if (checkGroupAnnColour && al.getGroups() != null
4459             && al.getGroups().size() > 0)
4460     {
4461       // pre 2.8.1 behaviour
4462       // check to see if we should transfer annotation colours
4463       propagateAnnColour = true;
4464       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4465       {
4466         if (sg.cs instanceof AnnotationColourGradient)
4467         {
4468           propagateAnnColour = false;
4469         }
4470       }
4471     }
4472     // int find annotation
4473     if (annAlignment.getAlignmentAnnotation() != null)
4474     {
4475       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4476       {
4477         if (annAlignment.getAlignmentAnnotation()[i].label
4478                 .equals(viewAnnColour.getAnnotation()))
4479         {
4480           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4481           {
4482             annAlignment.getAlignmentAnnotation()[i]
4483                     .setThreshold(new jalview.datamodel.GraphLine(
4484                             viewAnnColour.getThreshold(), "Threshold",
4485                             java.awt.Color.black)
4486
4487                     );
4488           }
4489
4490           if (viewAnnColour.getColourScheme().equals("None"))
4491           {
4492             cs = new AnnotationColourGradient(
4493                     annAlignment.getAlignmentAnnotation()[i],
4494                     new java.awt.Color(viewAnnColour.getMinColour()),
4495                     new java.awt.Color(viewAnnColour.getMaxColour()),
4496                     viewAnnColour.getAboveThreshold());
4497           }
4498           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4499           {
4500             cs = new AnnotationColourGradient(
4501                     annAlignment.getAlignmentAnnotation()[i],
4502                     getUserColourScheme(jms,
4503                             viewAnnColour.getColourScheme()),
4504                     viewAnnColour.getAboveThreshold());
4505           }
4506           else
4507           {
4508             cs = new AnnotationColourGradient(
4509                     annAlignment.getAlignmentAnnotation()[i],
4510                     ColourSchemeProperty.getColour(al,
4511                             viewAnnColour.getColourScheme()),
4512                     viewAnnColour.getAboveThreshold());
4513           }
4514           if (viewAnnColour.hasPerSequence())
4515           {
4516             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4517                     .isPerSequence());
4518           }
4519           if (viewAnnColour.hasPredefinedColours())
4520           {
4521             ((AnnotationColourGradient) cs)
4522                     .setPredefinedColours(viewAnnColour
4523                             .isPredefinedColours());
4524           }
4525           if (propagateAnnColour && al.getGroups() != null)
4526           {
4527             // Also use these settings for all the groups
4528             for (int g = 0; g < al.getGroups().size(); g++)
4529             {
4530               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4531
4532               if (sg.cs == null)
4533               {
4534                 continue;
4535               }
4536
4537               /*
4538                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4539                * new AnnotationColourGradient(
4540                * annAlignment.getAlignmentAnnotation()[i], new
4541                * java.awt.Color(viewAnnColour. getMinColour()), new
4542                * java.awt.Color(viewAnnColour. getMaxColour()),
4543                * viewAnnColour.getAboveThreshold()); } else
4544                */
4545               {
4546                 sg.cs = new AnnotationColourGradient(
4547                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4548                         viewAnnColour.getAboveThreshold());
4549                 if (cs instanceof AnnotationColourGradient)
4550                 {
4551                   if (viewAnnColour.hasPerSequence())
4552                   {
4553                     ((AnnotationColourGradient) cs)
4554                             .setSeqAssociated(viewAnnColour.isPerSequence());
4555                   }
4556                   if (viewAnnColour.hasPredefinedColours())
4557                   {
4558                     ((AnnotationColourGradient) cs)
4559                             .setPredefinedColours(viewAnnColour
4560                                     .isPredefinedColours());
4561                   }
4562                 }
4563               }
4564
4565             }
4566           }
4567
4568           break;
4569         }
4570
4571       }
4572     }
4573     return cs;
4574   }
4575
4576   private void reorderAutoannotation(AlignFrame af, Alignment al,
4577           List<JvAnnotRow> autoAlan)
4578   {
4579     // copy over visualization settings for autocalculated annotation in the
4580     // view
4581     if (al.getAlignmentAnnotation() != null)
4582     {
4583       /**
4584        * Kludge for magic autoannotation names (see JAL-811)
4585        */
4586       String[] magicNames = new String[] { "Consensus", "Quality",
4587           "Conservation" };
4588       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4589       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4590       for (String nm : magicNames)
4591       {
4592         visan.put(nm, nullAnnot);
4593       }
4594       for (JvAnnotRow auan : autoAlan)
4595       {
4596         visan.put(auan.template.label
4597                 + (auan.template.getCalcId() == null ? "" : "\t"
4598                         + auan.template.getCalcId()), auan);
4599       }
4600       int hSize = al.getAlignmentAnnotation().length;
4601       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4602       // work through any autoCalculated annotation already on the view
4603       // removing it if it should be placed in a different location on the
4604       // annotation panel.
4605       List<String> remains = new ArrayList<String>(visan.keySet());
4606       for (int h = 0; h < hSize; h++)
4607       {
4608         jalview.datamodel.AlignmentAnnotation jalan = al
4609                 .getAlignmentAnnotation()[h];
4610         if (jalan.autoCalculated)
4611         {
4612           String k;
4613           JvAnnotRow valan = visan.get(k = jalan.label);
4614           if (jalan.getCalcId() != null)
4615           {
4616             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4617           }
4618
4619           if (valan != null)
4620           {
4621             // delete the auto calculated row from the alignment
4622             al.deleteAnnotation(jalan, false);
4623             remains.remove(k);
4624             hSize--;
4625             h--;
4626             if (valan != nullAnnot)
4627             {
4628               if (jalan != valan.template)
4629               {
4630                 // newly created autoannotation row instance
4631                 // so keep a reference to the visible annotation row
4632                 // and copy over all relevant attributes
4633                 if (valan.template.graphHeight >= 0)
4634
4635                 {
4636                   jalan.graphHeight = valan.template.graphHeight;
4637                 }
4638                 jalan.visible = valan.template.visible;
4639               }
4640               reorder.add(new JvAnnotRow(valan.order, jalan));
4641             }
4642           }
4643         }
4644       }
4645       // Add any (possibly stale) autocalculated rows that were not appended to
4646       // the view during construction
4647       for (String other : remains)
4648       {
4649         JvAnnotRow othera = visan.get(other);
4650         if (othera != nullAnnot && othera.template.getCalcId() != null
4651                 && othera.template.getCalcId().length() > 0)
4652         {
4653           reorder.add(othera);
4654         }
4655       }
4656       // now put the automatic annotation in its correct place
4657       int s = 0, srt[] = new int[reorder.size()];
4658       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4659       for (JvAnnotRow jvar : reorder)
4660       {
4661         rws[s] = jvar;
4662         srt[s++] = jvar.order;
4663       }
4664       reorder.clear();
4665       jalview.util.QuickSort.sort(srt, rws);
4666       // and re-insert the annotation at its correct position
4667       for (JvAnnotRow jvar : rws)
4668       {
4669         al.addAnnotation(jvar.template, jvar.order);
4670       }
4671       af.alignPanel.adjustAnnotationHeight();
4672     }
4673   }
4674
4675   Hashtable skipList = null;
4676
4677   /**
4678    * TODO remove this method
4679    * 
4680    * @param view
4681    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4682    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4683    *         throw new Error("Implementation Error. No skipList defined for this
4684    *         Jalview2XML instance."); } return (AlignFrame)
4685    *         skipList.get(view.getSequenceSetId()); }
4686    */
4687
4688   /**
4689    * Check if the Jalview view contained in object should be skipped or not.
4690    * 
4691    * @param object
4692    * @return true if view's sequenceSetId is a key in skipList
4693    */
4694   private boolean skipViewport(JalviewModel object)
4695   {
4696     if (skipList == null)
4697     {
4698       return false;
4699     }
4700     String id;
4701     if (skipList.containsKey(id = object.getJalviewModelSequence()
4702             .getViewport()[0].getSequenceSetId()))
4703     {
4704       if (Cache.log != null && Cache.log.isDebugEnabled())
4705       {
4706         Cache.log.debug("Skipping seuqence set id " + id);
4707       }
4708       return true;
4709     }
4710     return false;
4711   }
4712
4713   public void addToSkipList(AlignFrame af)
4714   {
4715     if (skipList == null)
4716     {
4717       skipList = new Hashtable();
4718     }
4719     skipList.put(af.getViewport().getSequenceSetId(), af);
4720   }
4721
4722   public void clearSkipList()
4723   {
4724     if (skipList != null)
4725     {
4726       skipList.clear();
4727       skipList = null;
4728     }
4729   }
4730
4731   private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
4732           boolean ignoreUnrefed)
4733   {
4734     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
4735     Vector dseqs = null;
4736     if (ds == null)
4737     {
4738       // create a list of new dataset sequences
4739       dseqs = new Vector();
4740     }
4741     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4742     {
4743       Sequence vamsasSeq = vamsasSet.getSequence(i);
4744       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4745     }
4746     // create a new dataset
4747     if (ds == null)
4748     {
4749       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4750       dseqs.copyInto(dsseqs);
4751       ds = new jalview.datamodel.Alignment(dsseqs);
4752       debug("Created new dataset " + vamsasSet.getDatasetId()
4753               + " for alignment " + System.identityHashCode(al));
4754       addDatasetRef(vamsasSet.getDatasetId(), ds);
4755     }
4756     // set the dataset for the newly imported alignment.
4757     if (al.getDataset() == null && !ignoreUnrefed)
4758     {
4759       al.setDataset(ds);
4760     }
4761   }
4762
4763   /**
4764    * 
4765    * @param vamsasSeq
4766    *          sequence definition to create/merge dataset sequence for
4767    * @param ds
4768    *          dataset alignment
4769    * @param dseqs
4770    *          vector to add new dataset sequence to
4771    */
4772   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4773           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4774   {
4775     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4776     // xRef Codon Maps
4777     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4778     SequenceI dsq = null;
4779     if (sq != null && sq.getDatasetSequence() != null)
4780     {
4781       dsq = sq.getDatasetSequence();
4782     }
4783     if (sq == null && ignoreUnrefed)
4784     {
4785       return;
4786     }
4787     String sqid = vamsasSeq.getDsseqid();
4788     if (dsq == null)
4789     {
4790       // need to create or add a new dataset sequence reference to this sequence
4791       if (sqid != null)
4792       {
4793         dsq = seqRefIds.get(sqid);
4794       }
4795       // check again
4796       if (dsq == null)
4797       {
4798         // make a new dataset sequence
4799         dsq = sq.createDatasetSequence();
4800         if (sqid == null)
4801         {
4802           // make up a new dataset reference for this sequence
4803           sqid = seqHash(dsq);
4804         }
4805         dsq.setVamsasId(uniqueSetSuffix + sqid);
4806         seqRefIds.put(sqid, dsq);
4807         if (ds == null)
4808         {
4809           if (dseqs != null)
4810           {
4811             dseqs.addElement(dsq);
4812           }
4813         }
4814         else
4815         {
4816           ds.addSequence(dsq);
4817         }
4818       }
4819       else
4820       {
4821         if (sq != dsq)
4822         { // make this dataset sequence sq's dataset sequence
4823           sq.setDatasetSequence(dsq);
4824           // and update the current dataset alignment
4825           if (ds == null)
4826           {
4827             if (dseqs != null)
4828             {
4829               if (!dseqs.contains(dsq))
4830               {
4831                 dseqs.add(dsq);
4832               }
4833             }
4834             else
4835             {
4836               if (ds.findIndex(dsq) < 0)
4837               {
4838                 ds.addSequence(dsq);
4839               }
4840             }
4841           }
4842         }
4843       }
4844     }
4845     // TODO: refactor this as a merge dataset sequence function
4846     // now check that sq (the dataset sequence) sequence really is the union of
4847     // all references to it
4848     // boolean pre = sq.getStart() < dsq.getStart();
4849     // boolean post = sq.getEnd() > dsq.getEnd();
4850     // if (pre || post)
4851     if (sq != dsq)
4852     {
4853       // StringBuffer sb = new StringBuffer();
4854       String newres = jalview.analysis.AlignSeq.extractGaps(
4855               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4856       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4857               && newres.length() > dsq.getLength())
4858       {
4859         // Update with the longer sequence.
4860         synchronized (dsq)
4861         {
4862           /*
4863            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4864            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4865            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4866            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4867            */
4868           dsq.setSequence(newres);
4869         }
4870         // TODO: merges will never happen if we 'know' we have the real dataset
4871         // sequence - this should be detected when id==dssid
4872         System.err
4873                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4874         // + (pre ? "prepended" : "") + " "
4875         // + (post ? "appended" : ""));
4876       }
4877     }
4878   }
4879
4880   /*
4881    * TODO use AlignmentI here and in related methods - needs
4882    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
4883    */
4884   Hashtable<String, Alignment> datasetIds = null;
4885
4886   IdentityHashMap<Alignment, String> dataset2Ids = null;
4887
4888   private Alignment getDatasetFor(String datasetId)
4889   {
4890     if (datasetIds == null)
4891     {
4892       datasetIds = new Hashtable<String, Alignment>();
4893       return null;
4894     }
4895     if (datasetIds.containsKey(datasetId))
4896     {
4897       return datasetIds.get(datasetId);
4898     }
4899     return null;
4900   }
4901
4902   private void addDatasetRef(String datasetId, Alignment dataset)
4903   {
4904     if (datasetIds == null)
4905     {
4906       datasetIds = new Hashtable<String, Alignment>();
4907     }
4908     datasetIds.put(datasetId, dataset);
4909   }
4910
4911   /**
4912    * make a new dataset ID for this jalview dataset alignment
4913    * 
4914    * @param dataset
4915    * @return
4916    */
4917   private String getDatasetIdRef(Alignment dataset)
4918   {
4919     if (dataset.getDataset() != null)
4920     {
4921       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4922     }
4923     String datasetId = makeHashCode(dataset, null);
4924     if (datasetId == null)
4925     {
4926       // make a new datasetId and record it
4927       if (dataset2Ids == null)
4928       {
4929         dataset2Ids = new IdentityHashMap<Alignment, String>();
4930       }
4931       else
4932       {
4933         datasetId = dataset2Ids.get(dataset);
4934       }
4935       if (datasetId == null)
4936       {
4937         datasetId = "ds" + dataset2Ids.size() + 1;
4938         dataset2Ids.put(dataset, datasetId);
4939       }
4940     }
4941     return datasetId;
4942   }
4943
4944   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4945   {
4946     for (int d = 0; d < sequence.getDBRefCount(); d++)
4947     {
4948       DBRef dr = sequence.getDBRef(d);
4949       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4950               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4951                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4952       if (dr.getMapping() != null)
4953       {
4954         entry.setMap(addMapping(dr.getMapping()));
4955       }
4956       datasetSequence.addDBRef(entry);
4957     }
4958   }
4959
4960   private jalview.datamodel.Mapping addMapping(Mapping m)
4961   {
4962     SequenceI dsto = null;
4963     // Mapping m = dr.getMapping();
4964     int fr[] = new int[m.getMapListFromCount() * 2];
4965     Enumeration f = m.enumerateMapListFrom();
4966     for (int _i = 0; f.hasMoreElements(); _i += 2)
4967     {
4968       MapListFrom mf = (MapListFrom) f.nextElement();
4969       fr[_i] = mf.getStart();
4970       fr[_i + 1] = mf.getEnd();
4971     }
4972     int fto[] = new int[m.getMapListToCount() * 2];
4973     f = m.enumerateMapListTo();
4974     for (int _i = 0; f.hasMoreElements(); _i += 2)
4975     {
4976       MapListTo mf = (MapListTo) f.nextElement();
4977       fto[_i] = mf.getStart();
4978       fto[_i + 1] = mf.getEnd();
4979     }
4980     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
4981             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
4982     if (m.getMappingChoice() != null)
4983     {
4984       MappingChoice mc = m.getMappingChoice();
4985       if (mc.getDseqFor() != null)
4986       {
4987         String dsfor = "" + mc.getDseqFor();
4988         if (seqRefIds.containsKey(dsfor))
4989         {
4990           /**
4991            * recover from hash
4992            */
4993           jmap.setTo(seqRefIds.get(dsfor));
4994         }
4995         else
4996         {
4997           frefedSequence.add(new Object[] { dsfor, jmap });
4998         }
4999       }
5000       else
5001       {
5002         /**
5003          * local sequence definition
5004          */
5005         Sequence ms = mc.getSequence();
5006         SequenceI djs = null;
5007         String sqid = ms.getDsseqid();
5008         if (sqid != null && sqid.length() > 0)
5009         {
5010           /*
5011            * recover dataset sequence
5012            */
5013           djs = seqRefIds.get(sqid);
5014         }
5015         else
5016         {
5017           System.err
5018                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5019           sqid = ((Object) ms).toString(); // make up a new hascode for
5020           // undefined dataset sequence hash
5021           // (unlikely to happen)
5022         }
5023
5024         if (djs == null)
5025         {
5026           /**
5027            * make a new dataset sequence and add it to refIds hash
5028            */
5029           djs = new jalview.datamodel.Sequence(ms.getName(),
5030                   ms.getSequence());
5031           djs.setStart(jmap.getMap().getToLowest());
5032           djs.setEnd(jmap.getMap().getToHighest());
5033           djs.setVamsasId(uniqueSetSuffix + sqid);
5034           jmap.setTo(djs);
5035           seqRefIds.put(sqid, djs);
5036
5037         }
5038         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5039         addDBRefs(djs, ms);
5040
5041       }
5042     }
5043     return (jmap);
5044
5045   }
5046
5047   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5048           boolean keepSeqRefs)
5049   {
5050     initSeqRefs();
5051     JalviewModel jm = saveState(ap, null, null, null);
5052
5053     if (!keepSeqRefs)
5054     {
5055       clearSeqRefs();
5056       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5057     }
5058     else
5059     {
5060       uniqueSetSuffix = "";
5061       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5062       // overwrite the
5063       // view we just
5064       // copied
5065     }
5066     if (this.frefedSequence == null)
5067     {
5068       frefedSequence = new Vector();
5069     }
5070
5071     viewportsAdded.clear();
5072
5073     AlignFrame af = loadFromObject(jm, null, false, null);
5074     af.alignPanels.clear();
5075     af.closeMenuItem_actionPerformed(true);
5076
5077     /*
5078      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5079      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5080      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5081      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5082      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5083      */
5084
5085     return af.alignPanel;
5086   }
5087
5088   /**
5089    * flag indicating if hashtables should be cleared on finalization TODO this
5090    * flag may not be necessary
5091    */
5092   private final boolean _cleartables = true;
5093
5094   private Hashtable jvids2vobj;
5095
5096   /*
5097    * (non-Javadoc)
5098    * 
5099    * @see java.lang.Object#finalize()
5100    */
5101   @Override
5102   protected void finalize() throws Throwable
5103   {
5104     // really make sure we have no buried refs left.
5105     if (_cleartables)
5106     {
5107       clearSeqRefs();
5108     }
5109     this.seqRefIds = null;
5110     this.seqsToIds = null;
5111     super.finalize();
5112   }
5113
5114   private void warn(String msg)
5115   {
5116     warn(msg, null);
5117   }
5118
5119   private void warn(String msg, Exception e)
5120   {
5121     if (Cache.log != null)
5122     {
5123       if (e != null)
5124       {
5125         Cache.log.warn(msg, e);
5126       }
5127       else
5128       {
5129         Cache.log.warn(msg);
5130       }
5131     }
5132     else
5133     {
5134       System.err.println("Warning: " + msg);
5135       if (e != null)
5136       {
5137         e.printStackTrace();
5138       }
5139     }
5140   }
5141
5142   private void debug(String string)
5143   {
5144     debug(string, null);
5145   }
5146
5147   private void debug(String msg, Exception e)
5148   {
5149     if (Cache.log != null)
5150     {
5151       if (e != null)
5152       {
5153         Cache.log.debug(msg, e);
5154       }
5155       else
5156       {
5157         Cache.log.debug(msg);
5158       }
5159     }
5160     else
5161     {
5162       System.err.println("Warning: " + msg);
5163       if (e != null)
5164       {
5165         e.printStackTrace();
5166       }
5167     }
5168   }
5169
5170   /**
5171    * set the object to ID mapping tables used to write/recover objects and XML
5172    * ID strings for the jalview project. If external tables are provided then
5173    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5174    * object goes out of scope. - also populates the datasetIds hashtable with
5175    * alignment objects containing dataset sequences
5176    * 
5177    * @param vobj2jv
5178    *          Map from ID strings to jalview datamodel
5179    * @param jv2vobj
5180    *          Map from jalview datamodel to ID strings
5181    * 
5182    * 
5183    */
5184   public void setObjectMappingTables(Hashtable vobj2jv,
5185           IdentityHashMap jv2vobj)
5186   {
5187     this.jv2vobj = jv2vobj;
5188     this.vobj2jv = vobj2jv;
5189     Iterator ds = jv2vobj.keySet().iterator();
5190     String id;
5191     while (ds.hasNext())
5192     {
5193       Object jvobj = ds.next();
5194       id = jv2vobj.get(jvobj).toString();
5195       if (jvobj instanceof jalview.datamodel.Alignment)
5196       {
5197         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5198         {
5199           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5200         }
5201       }
5202       else if (jvobj instanceof jalview.datamodel.Sequence)
5203       {
5204         // register sequence object so the XML parser can recover it.
5205         if (seqRefIds == null)
5206         {
5207           seqRefIds = new HashMap<String, SequenceI>();
5208         }
5209         if (seqsToIds == null)
5210         {
5211           seqsToIds = new IdentityHashMap<SequenceI, String>();
5212         }
5213         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5214         seqsToIds.put((SequenceI) jvobj, id);
5215       }
5216       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5217       {
5218         String anid;
5219         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5220         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5221         if (jvann.annotationId == null)
5222         {
5223           jvann.annotationId = anid;
5224         }
5225         if (!jvann.annotationId.equals(anid))
5226         {
5227           // TODO verify that this is the correct behaviour
5228           this.warn("Overriding Annotation ID for " + anid
5229                   + " from different id : " + jvann.annotationId);
5230           jvann.annotationId = anid;
5231         }
5232       }
5233       else if (jvobj instanceof String)
5234       {
5235         if (jvids2vobj == null)
5236         {
5237           jvids2vobj = new Hashtable();
5238           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5239         }
5240       }
5241       else
5242       {
5243         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5244       }
5245     }
5246   }
5247
5248   /**
5249    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5250    * objects created from the project archive. If string is null (default for
5251    * construction) then suffix will be set automatically.
5252    * 
5253    * @param string
5254    */
5255   public void setUniqueSetSuffix(String string)
5256   {
5257     uniqueSetSuffix = string;
5258
5259   }
5260
5261   /**
5262    * uses skipList2 as the skipList for skipping views on sequence sets
5263    * associated with keys in the skipList
5264    * 
5265    * @param skipList2
5266    */
5267   public void setSkipList(Hashtable skipList2)
5268   {
5269     skipList = skipList2;
5270   }
5271
5272   /**
5273    * Reads the jar entry of given name and returns its contents, or null if the
5274    * entry is not found.
5275    * 
5276    * @param jprovider
5277    * @param jarEntryName
5278    * @return
5279    */
5280   protected String readJarEntry(jarInputStreamProvider jprovider,
5281           String jarEntryName)
5282   {
5283     String result = null;
5284     BufferedReader in = null;
5285
5286     try
5287     {
5288       /*
5289        * Reopen the jar input stream and traverse its entries to find a matching
5290        * name
5291        */
5292       JarInputStream jin = jprovider.getJarInputStream();
5293       JarEntry entry = null;
5294       do
5295       {
5296         entry = jin.getNextJarEntry();
5297       } while (entry != null && !entry.getName().equals(jarEntryName));
5298
5299       if (entry != null)
5300       {
5301         StringBuilder out = new StringBuilder(256);
5302         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5303         String data;
5304
5305         while ((data = in.readLine()) != null)
5306         {
5307           out.append(data);
5308         }
5309         result = out.toString();
5310       }
5311       else
5312       {
5313         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5314       }
5315     } catch (Exception ex)
5316     {
5317       ex.printStackTrace();
5318     } finally
5319     {
5320       if (in != null)
5321       {
5322         try
5323         {
5324           in.close();
5325         } catch (IOException e)
5326         {
5327           // ignore
5328         }
5329       }
5330     }
5331
5332     return result;
5333   }
5334
5335   /**
5336    * Returns an incrementing counter (0, 1, 2...)
5337    * 
5338    * @return
5339    */
5340   private synchronized int nextCounter()
5341   {
5342     return counter++;
5343   }
5344 }