Merge branch 'features/JAL-2110_crossRefDuplications' into merge_JAL-2110
[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.schemabinding.version2.AlcodMap;
40 import jalview.schemabinding.version2.AlcodonFrame;
41 import jalview.schemabinding.version2.Annotation;
42 import jalview.schemabinding.version2.AnnotationColours;
43 import jalview.schemabinding.version2.AnnotationElement;
44 import jalview.schemabinding.version2.CalcIdParam;
45 import jalview.schemabinding.version2.DBRef;
46 import jalview.schemabinding.version2.Features;
47 import jalview.schemabinding.version2.Group;
48 import jalview.schemabinding.version2.HiddenColumns;
49 import jalview.schemabinding.version2.JGroup;
50 import jalview.schemabinding.version2.JSeq;
51 import jalview.schemabinding.version2.JalviewModel;
52 import jalview.schemabinding.version2.JalviewModelSequence;
53 import jalview.schemabinding.version2.MapListFrom;
54 import jalview.schemabinding.version2.MapListTo;
55 import jalview.schemabinding.version2.Mapping;
56 import jalview.schemabinding.version2.MappingChoice;
57 import jalview.schemabinding.version2.OtherData;
58 import jalview.schemabinding.version2.PdbentryItem;
59 import jalview.schemabinding.version2.Pdbids;
60 import jalview.schemabinding.version2.Property;
61 import jalview.schemabinding.version2.RnaViewer;
62 import jalview.schemabinding.version2.SecondaryStructure;
63 import jalview.schemabinding.version2.Sequence;
64 import jalview.schemabinding.version2.SequenceSet;
65 import jalview.schemabinding.version2.SequenceSetProperties;
66 import jalview.schemabinding.version2.Setting;
67 import jalview.schemabinding.version2.StructureState;
68 import jalview.schemabinding.version2.ThresholdLine;
69 import jalview.schemabinding.version2.Tree;
70 import jalview.schemabinding.version2.UserColours;
71 import jalview.schemabinding.version2.Viewport;
72 import jalview.schemes.AnnotationColourGradient;
73 import jalview.schemes.ColourSchemeI;
74 import jalview.schemes.ColourSchemeProperty;
75 import jalview.schemes.FeatureColour;
76 import jalview.schemes.ResidueColourScheme;
77 import jalview.schemes.ResidueProperties;
78 import jalview.schemes.UserColourScheme;
79 import jalview.structure.StructureSelectionManager;
80 import jalview.structures.models.AAStructureBindingModel;
81 import jalview.util.MessageManager;
82 import jalview.util.Platform;
83 import jalview.util.StringUtils;
84 import jalview.util.jarInputStreamProvider;
85 import jalview.viewmodel.AlignmentViewport;
86 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
87 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
88 import jalview.ws.jws2.Jws2Discoverer;
89 import jalview.ws.jws2.dm.AAConSettings;
90 import jalview.ws.jws2.jabaws2.Jws2Instance;
91 import jalview.ws.params.ArgumentI;
92 import jalview.ws.params.AutoCalcSetting;
93 import jalview.ws.params.WsParamSetI;
94
95 import java.awt.Color;
96 import java.awt.Rectangle;
97 import java.io.BufferedReader;
98 import java.io.DataInputStream;
99 import java.io.DataOutputStream;
100 import java.io.File;
101 import java.io.FileInputStream;
102 import java.io.FileOutputStream;
103 import java.io.IOException;
104 import java.io.InputStreamReader;
105 import java.io.OutputStreamWriter;
106 import java.io.PrintWriter;
107 import java.lang.reflect.InvocationTargetException;
108 import java.net.MalformedURLException;
109 import java.net.URL;
110 import java.util.ArrayList;
111 import java.util.Enumeration;
112 import java.util.HashMap;
113 import java.util.HashSet;
114 import java.util.Hashtable;
115 import java.util.IdentityHashMap;
116 import java.util.Iterator;
117 import java.util.LinkedHashMap;
118 import java.util.List;
119 import java.util.Map;
120 import java.util.Map.Entry;
121 import java.util.Set;
122 import java.util.Vector;
123 import java.util.jar.JarEntry;
124 import java.util.jar.JarInputStream;
125 import java.util.jar.JarOutputStream;
126
127 import javax.swing.JInternalFrame;
128 import javax.swing.JOptionPane;
129 import javax.swing.SwingUtilities;
130
131 import org.exolab.castor.xml.Marshaller;
132 import org.exolab.castor.xml.Unmarshaller;
133
134 /**
135  * Write out the current jalview desktop state as a Jalview XML stream.
136  * 
137  * Note: the vamsas objects referred to here are primitive versions of the
138  * VAMSAS project schema elements - they are not the same and most likely never
139  * will be :)
140  * 
141  * @author $author$
142  * @version $Revision: 1.134 $
143  */
144 public class Jalview2XML
145 {
146   private static final String VIEWER_PREFIX = "viewer_";
147
148   private static final String RNA_PREFIX = "rna_";
149
150   private static final String UTF_8 = "UTF-8";
151
152   // use this with nextCounter() to make unique names for entities
153   private int counter = 0;
154
155   /*
156    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
157    * of sequence objects are created.
158    */
159   IdentityHashMap<SequenceI, String> seqsToIds = null;
160
161   /**
162    * jalview XML Sequence ID to jalview sequence object reference (both dataset
163    * and alignment sequences. Populated as XML reps of sequence objects are
164    * created.)
165    */
166   Map<String, SequenceI> seqRefIds = null;
167
168   Vector<Object[]> frefedSequence = null;
169
170   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
171
172   /*
173    * Map of reconstructed AlignFrame objects that appear to have come from
174    * SplitFrame objects (have a dna/protein complement view).
175    */
176   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<Viewport, AlignFrame>();
177
178   /*
179    * Map from displayed rna structure models to their saved session state jar
180    * entry names
181    */
182   private Map<RnaModel, String> rnaSessions = new HashMap<RnaModel, String>();
183
184   /**
185    * create/return unique hash string for sq
186    * 
187    * @param sq
188    * @return new or existing unique string for sq
189    */
190   String seqHash(SequenceI sq)
191   {
192     if (seqsToIds == null)
193     {
194       initSeqRefs();
195     }
196     if (seqsToIds.containsKey(sq))
197     {
198       return seqsToIds.get(sq);
199     }
200     else
201     {
202       // create sequential key
203       String key = "sq" + (seqsToIds.size() + 1);
204       key = makeHashCode(sq, key); // check we don't have an external reference
205       // for it already.
206       seqsToIds.put(sq, key);
207       return key;
208     }
209   }
210
211   void clearSeqRefs()
212   {
213     if (_cleartables)
214     {
215       if (seqRefIds != null)
216       {
217         seqRefIds.clear();
218       }
219       if (seqsToIds != null)
220       {
221         seqsToIds.clear();
222       }
223       // seqRefIds = null;
224       // seqsToIds = null;
225     }
226     else
227     {
228       // do nothing
229       warn("clearSeqRefs called when _cleartables was not set. Doing nothing.");
230       // seqRefIds = new Hashtable();
231       // seqsToIds = new IdentityHashMap();
232     }
233   }
234
235   void initSeqRefs()
236   {
237     if (seqsToIds == null)
238     {
239       seqsToIds = new IdentityHashMap<SequenceI, String>();
240     }
241     if (seqRefIds == null)
242     {
243       seqRefIds = new HashMap<String, SequenceI>();
244     }
245   }
246
247   public Jalview2XML()
248   {
249   }
250
251   public Jalview2XML(boolean raiseGUI)
252   {
253     this.raiseGUI = raiseGUI;
254   }
255
256   public void resolveFrefedSequences()
257   {
258     if (frefedSequence.size() > 0)
259     {
260       int r = 0, rSize = frefedSequence.size();
261       while (r < rSize)
262       {
263         Object[] ref = frefedSequence.elementAt(r);
264         if (ref != null)
265         {
266           String sref = (String) ref[0];
267           if (seqRefIds.containsKey(sref))
268           {
269             if (ref[1] instanceof jalview.datamodel.Mapping)
270             {
271               SequenceI seq = seqRefIds.get(sref);
272               while (seq.getDatasetSequence() != null)
273               {
274                 seq = seq.getDatasetSequence();
275               }
276               ((jalview.datamodel.Mapping) ref[1]).setTo(seq);
277             }
278             else
279             {
280               if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
281               {
282                 SequenceI seq = seqRefIds.get(sref);
283                 while (seq.getDatasetSequence() != null)
284                 {
285                   seq = seq.getDatasetSequence();
286                 }
287                 if (ref[2] != null
288                         && ref[2] instanceof jalview.datamodel.Mapping)
289                 {
290                   jalview.datamodel.Mapping mp = (jalview.datamodel.Mapping) ref[2];
291                   ((jalview.datamodel.AlignedCodonFrame) ref[1]).addMap(
292                           seq, mp.getTo(), mp.getMap());
293                 }
294                 else
295                 {
296                   System.err
297                           .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for AlcodonFrames involving "
298                                   + ref[2].getClass() + " type objects.");
299                 }
300               }
301               else
302               {
303                 System.err
304                         .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for "
305                                 + ref[1].getClass() + " type objects.");
306               }
307             }
308             frefedSequence.remove(r);
309             rSize--;
310           }
311           else
312           {
313             System.err
314                     .println("IMPLEMENTATION WARNING: Unresolved forward reference for hash string "
315                             + ref[0]
316                             + " with objecttype "
317                             + ref[1].getClass());
318             r++;
319           }
320         }
321         else
322         {
323           // empty reference
324           frefedSequence.remove(r);
325           rSize--;
326         }
327       }
328     }
329   }
330
331   /**
332    * This maintains a map of viewports, the key being the seqSetId. Important to
333    * set historyItem and redoList for multiple views
334    */
335   Map<String, AlignViewport> viewportsAdded = new HashMap<String, AlignViewport>();
336
337   Map<String, AlignmentAnnotation> annotationIds = new HashMap<String, AlignmentAnnotation>();
338
339   String uniqueSetSuffix = "";
340
341   /**
342    * List of pdbfiles added to Jar
343    */
344   List<String> pdbfiles = null;
345
346   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
347   public void saveState(File statefile)
348   {
349     FileOutputStream fos = null;
350     try
351     {
352       fos = new FileOutputStream(statefile);
353       JarOutputStream jout = new JarOutputStream(fos);
354       saveState(jout);
355
356     } catch (Exception e)
357     {
358       // TODO: inform user of the problem - they need to know if their data was
359       // not saved !
360       if (errorMessage == null)
361       {
362         errorMessage = "Couldn't write Jalview Archive to output file '"
363                 + statefile + "' - See console error log for details";
364       }
365       else
366       {
367         errorMessage += "(output file was '" + statefile + "')";
368       }
369       e.printStackTrace();
370     } finally
371     {
372       if (fos != null)
373       {
374         try
375         {
376           fos.close();
377         } catch (IOException e)
378         {
379           // ignore
380         }
381       }
382     }
383     reportErrors();
384   }
385
386   /**
387    * Writes a jalview project archive to the given Jar output stream.
388    * 
389    * @param jout
390    */
391   public void saveState(JarOutputStream jout)
392   {
393     AlignFrame[] frames = Desktop.getAlignFrames();
394
395     if (frames == null)
396     {
397       return;
398     }
399
400     Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
401
402     /*
403      * ensure cached data is clear before starting
404      */
405     // todo tidy up seqRefIds, seqsToIds initialisation / reset
406     rnaSessions.clear();
407     splitFrameCandidates.clear();
408
409     try
410     {
411
412       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
413       // //////////////////////////////////////////////////
414
415       List<String> shortNames = new ArrayList<String>();
416       List<String> viewIds = new ArrayList<String>();
417
418       // REVERSE ORDER
419       for (int i = frames.length - 1; i > -1; i--)
420       {
421         AlignFrame af = frames[i];
422         // skip ?
423         if (skipList != null
424                 && skipList
425                         .containsKey(af.getViewport().getSequenceSetId()))
426         {
427           continue;
428         }
429
430         String shortName = makeFilename(af, shortNames);
431
432         int ap, apSize = af.alignPanels.size();
433
434         for (ap = 0; ap < apSize; ap++)
435         {
436           AlignmentPanel apanel = af.alignPanels.get(ap);
437           String fileName = apSize == 1 ? shortName : ap + shortName;
438           if (!fileName.endsWith(".xml"))
439           {
440             fileName = fileName + ".xml";
441           }
442
443           saveState(apanel, fileName, jout, viewIds);
444
445           String dssid = getDatasetIdRef(af.getViewport().getAlignment()
446                   .getDataset());
447           if (!dsses.containsKey(dssid))
448           {
449             dsses.put(dssid, af);
450           }
451         }
452       }
453
454       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
455               jout);
456
457       try
458       {
459         jout.flush();
460       } catch (Exception foo)
461       {
462       }
463       ;
464       jout.close();
465     } catch (Exception ex)
466     {
467       // TODO: inform user of the problem - they need to know if their data was
468       // not saved !
469       if (errorMessage == null)
470       {
471         errorMessage = "Couldn't write Jalview Archive - see error output for details";
472       }
473       ex.printStackTrace();
474     }
475   }
476
477   /**
478    * Generates a distinct file name, based on the title of the AlignFrame, by
479    * appending _n for increasing n until an unused name is generated. The new
480    * name (without its extension) is added to the list.
481    * 
482    * @param af
483    * @param namesUsed
484    * @return the generated name, with .xml extension
485    */
486   protected String makeFilename(AlignFrame af, List<String> namesUsed)
487   {
488     String shortName = af.getTitle();
489
490     if (shortName.indexOf(File.separatorChar) > -1)
491     {
492       shortName = shortName.substring(shortName
493               .lastIndexOf(File.separatorChar) + 1);
494     }
495
496     int count = 1;
497
498     while (namesUsed.contains(shortName))
499     {
500       if (shortName.endsWith("_" + (count - 1)))
501       {
502         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
503       }
504
505       shortName = shortName.concat("_" + count);
506       count++;
507     }
508
509     namesUsed.add(shortName);
510
511     if (!shortName.endsWith(".xml"))
512     {
513       shortName = shortName + ".xml";
514     }
515     return shortName;
516   }
517
518   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
519   public boolean saveAlignment(AlignFrame af, String jarFile,
520           String fileName)
521   {
522     try
523     {
524       int ap = 0;
525       int apSize = af.alignPanels.size();
526       FileOutputStream fos = new FileOutputStream(jarFile);
527       JarOutputStream jout = new JarOutputStream(fos);
528       Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
529       List<String> viewIds = new ArrayList<String>();
530
531       for (AlignmentPanel apanel : af.alignPanels)
532       {
533         String jfileName = apSize == 1 ? fileName : fileName + ap;
534         ap++;
535         if (!jfileName.endsWith(".xml"))
536         {
537           jfileName = jfileName + ".xml";
538         }
539         saveState(apanel, jfileName, jout, viewIds);
540         String dssid = getDatasetIdRef(af.getViewport().getAlignment()
541                 .getDataset());
542         if (!dsses.containsKey(dssid))
543         {
544           dsses.put(dssid, af);
545         }
546       }
547       writeDatasetFor(dsses, fileName, jout);
548       try
549       {
550         jout.flush();
551       } catch (Exception foo)
552       {
553       }
554       ;
555       jout.close();
556       return true;
557     } catch (Exception ex)
558     {
559       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
560       ex.printStackTrace();
561       return false;
562     }
563   }
564
565   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
566           String fileName, JarOutputStream jout)
567   {
568
569     for (String dssids : dsses.keySet())
570     {
571       AlignFrame _af = dsses.get(dssids);
572       String jfileName = fileName + " Dataset for " + _af.getTitle();
573       if (!jfileName.endsWith(".xml"))
574       {
575         jfileName = jfileName + ".xml";
576       }
577       saveState(_af.alignPanel, jfileName, true, jout, null);
578     }
579   }
580
581   /**
582    * create a JalviewModel from an alignment view and marshall it to a
583    * JarOutputStream
584    * 
585    * @param ap
586    *          panel to create jalview model for
587    * @param fileName
588    *          name of alignment panel written to output stream
589    * @param jout
590    *          jar output stream
591    * @param viewIds
592    * @param out
593    *          jar entry name
594    */
595   public JalviewModel saveState(AlignmentPanel ap, String fileName,
596           JarOutputStream jout, List<String> viewIds)
597   {
598     return saveState(ap, fileName, false, jout, viewIds);
599   }
600
601   /**
602    * create a JalviewModel from an alignment view and marshall it to a
603    * JarOutputStream
604    * 
605    * @param ap
606    *          panel to create jalview model for
607    * @param fileName
608    *          name of alignment panel written to output stream
609    * @param storeDS
610    *          when true, only write the dataset for the alignment, not the data
611    *          associated with the view.
612    * @param jout
613    *          jar output stream
614    * @param out
615    *          jar entry name
616    */
617   public JalviewModel saveState(AlignmentPanel ap, String fileName,
618           boolean storeDS, JarOutputStream jout, List<String> viewIds)
619   {
620     if (viewIds == null)
621     {
622       viewIds = new ArrayList<String>();
623     }
624
625     initSeqRefs();
626
627     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
628
629     AlignViewport av = ap.av;
630
631     JalviewModel object = new JalviewModel();
632     object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
633
634     object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
635     object.setVersion(jalview.bin.Cache.getDefault("VERSION",
636             "Development Build"));
637
638     /**
639      * rjal is full height alignment, jal is actual alignment with full metadata
640      * but excludes hidden sequences.
641      */
642     jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
643
644     if (av.hasHiddenRows())
645     {
646       rjal = jal.getHiddenSequences().getFullAlignment();
647     }
648
649     SequenceSet vamsasSet = new SequenceSet();
650     Sequence vamsasSeq;
651     JalviewModelSequence jms = new JalviewModelSequence();
652
653     vamsasSet.setGapChar(jal.getGapCharacter() + "");
654
655     if (jal.getDataset() != null)
656     {
657       // dataset id is the dataset's hashcode
658       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
659       if (storeDS)
660       {
661         // switch jal and the dataset
662         jal = jal.getDataset();
663         rjal = jal;
664       }
665     }
666     if (jal.getProperties() != null)
667     {
668       Enumeration en = jal.getProperties().keys();
669       while (en.hasMoreElements())
670       {
671         String key = en.nextElement().toString();
672         SequenceSetProperties ssp = new SequenceSetProperties();
673         ssp.setKey(key);
674         ssp.setValue(jal.getProperties().get(key).toString());
675         vamsasSet.addSequenceSetProperties(ssp);
676       }
677     }
678
679     JSeq jseq;
680     Set<String> calcIdSet = new HashSet<String>();
681
682     // SAVE SEQUENCES
683     for (int i = 0; i < rjal.getHeight(); i++)
684     {
685       final SequenceI jds = rjal.getSequenceAt(i);
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(rjal.getSequenceAt(i)))
729           {
730             jalview.datamodel.SequenceI[] reps = av
731                     .getRepresentedSequences(rjal.getSequenceAt(i))
732                     .getSequencesInOrder(rjal);
733
734             for (int h = 0; h < reps.length; h++)
735             {
736               if (reps[h] != rjal.getSequenceAt(i))
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(rjal.getSequenceAt(i) == 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 properties = entry.getProperty();
870             Enumeration en2 = properties.keys();
871             while (en2.hasMoreElements())
872             {
873               Property prop = new Property();
874               String key = en2.nextElement().toString();
875               prop.setName(key);
876               prop.setValue(properties.get(key).toString());
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     AlignmentI 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,
3993               jalview.io.AppletFormatAdapter.FILE);
3994       binding.addSequenceForStructFile(pdbFile, seq);
3995     }
3996     // and add the AlignmentPanel's reference to the view panel
3997     viewer.addAlignmentPanel(ap);
3998     if (useinViewerSuperpos)
3999     {
4000       viewer.useAlignmentPanelForSuperposition(ap);
4001     }
4002     else
4003     {
4004       viewer.excludeAlignmentPanelForSuperposition(ap);
4005     }
4006     if (usetoColourbyseq)
4007     {
4008       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4009     }
4010     else
4011     {
4012       viewer.excludeAlignmentPanelForColourbyseq(ap);
4013     }
4014   }
4015
4016   /**
4017    * Get all frames within the Desktop.
4018    * 
4019    * @return
4020    */
4021   protected JInternalFrame[] getAllFrames()
4022   {
4023     JInternalFrame[] frames = null;
4024     // TODO is this necessary - is it safe - risk of hanging?
4025     do
4026     {
4027       try
4028       {
4029         frames = Desktop.desktop.getAllFrames();
4030       } catch (ArrayIndexOutOfBoundsException e)
4031       {
4032         // occasional No such child exceptions are thrown here...
4033         try
4034         {
4035           Thread.sleep(10);
4036         } catch (InterruptedException f)
4037         {
4038         }
4039       }
4040     } while (frames == null);
4041     return frames;
4042   }
4043
4044   /**
4045    * Answers true if 'version' is equal to or later than 'supported', where each
4046    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4047    * changes. Development and test values for 'version' are leniently treated
4048    * i.e. answer true.
4049    * 
4050    * @param supported
4051    *          - minimum version we are comparing against
4052    * @param version
4053    *          - version of data being processsed
4054    * @return
4055    */
4056   public static boolean isVersionStringLaterThan(String supported,
4057           String version)
4058   {
4059     if (supported == null || version == null
4060             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4061             || version.equalsIgnoreCase("Test")
4062             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4063     {
4064       System.err.println("Assuming project file with "
4065               + (version == null ? "null" : version)
4066               + " is compatible with Jalview version " + supported);
4067       return true;
4068     }
4069     else
4070     {
4071       return StringUtils.compareVersions(version, supported, "b") >= 0;
4072     }
4073   }
4074
4075   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4076
4077   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4078   {
4079     if (newStructureViewers != null)
4080     {
4081       sview.getBinding().setFinishedLoadingFromArchive(false);
4082       newStructureViewers.add(sview);
4083     }
4084   }
4085
4086   protected void setLoadingFinishedForNewStructureViewers()
4087   {
4088     if (newStructureViewers != null)
4089     {
4090       for (JalviewStructureDisplayI sview : newStructureViewers)
4091       {
4092         sview.getBinding().setFinishedLoadingFromArchive(true);
4093       }
4094       newStructureViewers.clear();
4095       newStructureViewers = null;
4096     }
4097   }
4098
4099   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4100           List<SequenceI> hiddenSeqs, AlignmentI al,
4101           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4102           String viewId, List<JvAnnotRow> autoAlan)
4103   {
4104     AlignFrame af = null;
4105     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4106             uniqueSeqSetId, viewId);
4107
4108     af.setFileName(file, "Jalview");
4109
4110     for (int i = 0; i < JSEQ.length; i++)
4111     {
4112       af.viewport.setSequenceColour(af.viewport.getAlignment()
4113               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4114     }
4115
4116     if (al.hasSeqrep())
4117     {
4118       af.getViewport().setColourByReferenceSeq(true);
4119       af.getViewport().setDisplayReferenceSeq(true);
4120     }
4121
4122     af.viewport.setGatherViewsHere(view.getGatheredViews());
4123
4124     if (view.getSequenceSetId() != null)
4125     {
4126       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4127
4128       af.viewport.setSequenceSetId(uniqueSeqSetId);
4129       if (av != null)
4130       {
4131         // propagate shared settings to this new view
4132         af.viewport.setHistoryList(av.getHistoryList());
4133         af.viewport.setRedoList(av.getRedoList());
4134       }
4135       else
4136       {
4137         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4138       }
4139       // TODO: check if this method can be called repeatedly without
4140       // side-effects if alignpanel already registered.
4141       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4142     }
4143     // apply Hidden regions to view.
4144     if (hiddenSeqs != null)
4145     {
4146       for (int s = 0; s < JSEQ.length; s++)
4147       {
4148         jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
4149
4150         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4151         {
4152           hidden.addSequence(
4153                   al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
4154         }
4155         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
4156       }
4157
4158       // jalview.datamodel.SequenceI[] hseqs = new
4159       // jalview.datamodel.SequenceI[hiddenSeqs
4160       // .size()];
4161       //
4162       // for (int s = 0; s < hiddenSeqs.size(); s++)
4163       // {
4164       // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
4165       // }
4166
4167       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4168               .size()]);
4169       af.viewport.hideSequence(hseqs);
4170
4171     }
4172     // recover view properties and display parameters
4173     if (view.getViewName() != null)
4174     {
4175       af.viewport.viewName = view.getViewName();
4176       af.setInitialTabVisible();
4177     }
4178     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4179             view.getHeight());
4180
4181     af.viewport.setShowAnnotation(view.getShowAnnotation());
4182     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4183
4184     af.viewport.setColourText(view.getShowColourText());
4185
4186     af.viewport.setConservationSelected(view.getConservationSelected());
4187     af.viewport.setShowJVSuffix(view.getShowFullId());
4188     af.viewport.setRightAlignIds(view.getRightAlignIds());
4189     af.viewport.setFont(
4190             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4191                     .getFontSize()), true);
4192     ViewStyleI vs = af.viewport.getViewStyle();
4193     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4194     af.viewport.setViewStyle(vs);
4195     // TODO: allow custom charWidth/Heights to be restored by updating them
4196     // after setting font - which means set above to false
4197     af.viewport.setRenderGaps(view.getRenderGaps());
4198     af.viewport.setWrapAlignment(view.getWrapAlignment());
4199     af.viewport.setShowAnnotation(view.getShowAnnotation());
4200
4201     af.viewport.setShowBoxes(view.getShowBoxes());
4202
4203     af.viewport.setShowText(view.getShowText());
4204
4205     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4206     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4207     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4208     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4209             .isShowUnconserved() : false);
4210     af.viewport.setStartRes(view.getStartRes());
4211     af.viewport.setStartSeq(view.getStartSeq());
4212     af.alignPanel.updateLayout();
4213     ColourSchemeI cs = null;
4214     // apply colourschemes
4215     if (view.getBgColour() != null)
4216     {
4217       if (view.getBgColour().startsWith("ucs"))
4218       {
4219         cs = getUserColourScheme(jms, view.getBgColour());
4220       }
4221       else if (view.getBgColour().startsWith("Annotation"))
4222       {
4223         AnnotationColours viewAnnColour = view.getAnnotationColours();
4224         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4225
4226         // annpos
4227
4228       }
4229       else
4230       {
4231         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4232       }
4233
4234       if (cs != null)
4235       {
4236         cs.setThreshold(view.getPidThreshold(), true);
4237         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4238       }
4239     }
4240
4241     af.viewport.setGlobalColourScheme(cs);
4242     af.viewport.setColourAppliesToAllGroups(false);
4243
4244     if (view.getConservationSelected() && cs != null)
4245     {
4246       cs.setConservationInc(view.getConsThreshold());
4247     }
4248
4249     af.changeColour(cs);
4250
4251     af.viewport.setColourAppliesToAllGroups(true);
4252
4253     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4254
4255     if (view.hasCentreColumnLabels())
4256     {
4257       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4258     }
4259     if (view.hasIgnoreGapsinConsensus())
4260     {
4261       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4262               null);
4263     }
4264     if (view.hasFollowHighlight())
4265     {
4266       af.viewport.setFollowHighlight(view.getFollowHighlight());
4267     }
4268     if (view.hasFollowSelection())
4269     {
4270       af.viewport.followSelection = view.getFollowSelection();
4271     }
4272     if (view.hasShowConsensusHistogram())
4273     {
4274       af.viewport.setShowConsensusHistogram(view
4275               .getShowConsensusHistogram());
4276     }
4277     else
4278     {
4279       af.viewport.setShowConsensusHistogram(true);
4280     }
4281     if (view.hasShowSequenceLogo())
4282     {
4283       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4284     }
4285     else
4286     {
4287       af.viewport.setShowSequenceLogo(false);
4288     }
4289     if (view.hasNormaliseSequenceLogo())
4290     {
4291       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4292     }
4293     if (view.hasShowDbRefTooltip())
4294     {
4295       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4296     }
4297     if (view.hasShowNPfeatureTooltip())
4298     {
4299       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4300     }
4301     if (view.hasShowGroupConsensus())
4302     {
4303       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4304     }
4305     else
4306     {
4307       af.viewport.setShowGroupConsensus(false);
4308     }
4309     if (view.hasShowGroupConservation())
4310     {
4311       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4312     }
4313     else
4314     {
4315       af.viewport.setShowGroupConservation(false);
4316     }
4317
4318     // recover featre settings
4319     if (jms.getFeatureSettings() != null)
4320     {
4321       FeaturesDisplayed fdi;
4322       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4323       String[] renderOrder = new String[jms.getFeatureSettings()
4324               .getSettingCount()];
4325       Map<String, FeatureColourI> featureColours = new Hashtable<String, FeatureColourI>();
4326       Map<String, Float> featureOrder = new Hashtable<String, Float>();
4327
4328       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4329       {
4330         Setting setting = jms.getFeatureSettings().getSetting(fs);
4331         if (setting.hasMincolour())
4332         {
4333           FeatureColourI gc = setting.hasMin() ? new FeatureColour(
4334                   new Color(setting.getMincolour()), new Color(
4335                           setting.getColour()), setting.getMin(),
4336                   setting.getMax()) : new FeatureColour(new Color(
4337                   setting.getMincolour()), new Color(setting.getColour()),
4338                   0, 1);
4339           if (setting.hasThreshold())
4340           {
4341             gc.setThreshold(setting.getThreshold());
4342             int threshstate = setting.getThreshstate();
4343             // -1 = None, 0 = Below, 1 = Above threshold
4344             if (threshstate == 0)
4345             {
4346               gc.setBelowThreshold(true);
4347             }
4348             else if (threshstate == 1)
4349             {
4350               gc.setAboveThreshold(true);
4351             }
4352           }
4353           gc.setAutoScaled(true); // default
4354           if (setting.hasAutoScale())
4355           {
4356             gc.setAutoScaled(setting.getAutoScale());
4357           }
4358           if (setting.hasColourByLabel())
4359           {
4360             gc.setColourByLabel(setting.getColourByLabel());
4361           }
4362           // and put in the feature colour table.
4363           featureColours.put(setting.getType(), gc);
4364         }
4365         else
4366         {
4367           featureColours.put(setting.getType(), new FeatureColour(
4368                   new Color(setting.getColour())));
4369         }
4370         renderOrder[fs] = setting.getType();
4371         if (setting.hasOrder())
4372         {
4373           featureOrder.put(setting.getType(), setting.getOrder());
4374         }
4375         else
4376         {
4377           featureOrder.put(setting.getType(), new Float(fs
4378                   / jms.getFeatureSettings().getSettingCount()));
4379         }
4380         if (setting.getDisplay())
4381         {
4382           fdi.setVisible(setting.getType());
4383         }
4384       }
4385       Map<String, Boolean> fgtable = new Hashtable<String, Boolean>();
4386       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4387       {
4388         Group grp = jms.getFeatureSettings().getGroup(gs);
4389         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4390       }
4391       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4392       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4393       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4394       FeatureRendererSettings frs = new FeatureRendererSettings(
4395               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4396       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4397               .transferSettings(frs);
4398
4399     }
4400
4401     if (view.getHiddenColumnsCount() > 0)
4402     {
4403       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4404       {
4405         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4406                 .getHiddenColumns(c).getEnd() // +1
4407                 );
4408       }
4409     }
4410     if (view.getCalcIdParam() != null)
4411     {
4412       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4413       {
4414         if (calcIdParam != null)
4415         {
4416           if (recoverCalcIdParam(calcIdParam, af.viewport))
4417           {
4418           }
4419           else
4420           {
4421             warn("Couldn't recover parameters for "
4422                     + calcIdParam.getCalcId());
4423           }
4424         }
4425       }
4426     }
4427     af.setMenusFromViewport(af.viewport);
4428
4429     // TODO: we don't need to do this if the viewport is aready visible.
4430     /*
4431      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4432      * has a 'cdna/protein complement' view, in which case save it in order to
4433      * populate a SplitFrame once all views have been read in.
4434      */
4435     String complementaryViewId = view.getComplementId();
4436     if (complementaryViewId == null)
4437     {
4438       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4439               view.getHeight());
4440       // recompute any autoannotation
4441       af.alignPanel.updateAnnotation(false, true);
4442       reorderAutoannotation(af, al, autoAlan);
4443       af.alignPanel.alignmentChanged();
4444     }
4445     else
4446     {
4447       splitFrameCandidates.put(view, af);
4448     }
4449     return af;
4450   }
4451
4452   private ColourSchemeI constructAnnotationColour(
4453           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
4454           JalviewModelSequence jms, boolean checkGroupAnnColour)
4455   {
4456     boolean propagateAnnColour = false;
4457     ColourSchemeI cs = null;
4458     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4459     if (checkGroupAnnColour && al.getGroups() != null
4460             && al.getGroups().size() > 0)
4461     {
4462       // pre 2.8.1 behaviour
4463       // check to see if we should transfer annotation colours
4464       propagateAnnColour = true;
4465       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4466       {
4467         if (sg.cs instanceof AnnotationColourGradient)
4468         {
4469           propagateAnnColour = false;
4470         }
4471       }
4472     }
4473     // int find annotation
4474     if (annAlignment.getAlignmentAnnotation() != null)
4475     {
4476       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4477       {
4478         if (annAlignment.getAlignmentAnnotation()[i].label
4479                 .equals(viewAnnColour.getAnnotation()))
4480         {
4481           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4482           {
4483             annAlignment.getAlignmentAnnotation()[i]
4484                     .setThreshold(new jalview.datamodel.GraphLine(
4485                             viewAnnColour.getThreshold(), "Threshold",
4486                             java.awt.Color.black)
4487
4488                     );
4489           }
4490
4491           if (viewAnnColour.getColourScheme().equals("None"))
4492           {
4493             cs = new AnnotationColourGradient(
4494                     annAlignment.getAlignmentAnnotation()[i],
4495                     new java.awt.Color(viewAnnColour.getMinColour()),
4496                     new java.awt.Color(viewAnnColour.getMaxColour()),
4497                     viewAnnColour.getAboveThreshold());
4498           }
4499           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4500           {
4501             cs = new AnnotationColourGradient(
4502                     annAlignment.getAlignmentAnnotation()[i],
4503                     getUserColourScheme(jms,
4504                             viewAnnColour.getColourScheme()),
4505                     viewAnnColour.getAboveThreshold());
4506           }
4507           else
4508           {
4509             cs = new AnnotationColourGradient(
4510                     annAlignment.getAlignmentAnnotation()[i],
4511                     ColourSchemeProperty.getColour(al,
4512                             viewAnnColour.getColourScheme()),
4513                     viewAnnColour.getAboveThreshold());
4514           }
4515           if (viewAnnColour.hasPerSequence())
4516           {
4517             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4518                     .isPerSequence());
4519           }
4520           if (viewAnnColour.hasPredefinedColours())
4521           {
4522             ((AnnotationColourGradient) cs)
4523                     .setPredefinedColours(viewAnnColour
4524                             .isPredefinedColours());
4525           }
4526           if (propagateAnnColour && al.getGroups() != null)
4527           {
4528             // Also use these settings for all the groups
4529             for (int g = 0; g < al.getGroups().size(); g++)
4530             {
4531               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4532
4533               if (sg.cs == null)
4534               {
4535                 continue;
4536               }
4537
4538               /*
4539                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4540                * new AnnotationColourGradient(
4541                * annAlignment.getAlignmentAnnotation()[i], new
4542                * java.awt.Color(viewAnnColour. getMinColour()), new
4543                * java.awt.Color(viewAnnColour. getMaxColour()),
4544                * viewAnnColour.getAboveThreshold()); } else
4545                */
4546               {
4547                 sg.cs = new AnnotationColourGradient(
4548                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4549                         viewAnnColour.getAboveThreshold());
4550                 if (cs instanceof AnnotationColourGradient)
4551                 {
4552                   if (viewAnnColour.hasPerSequence())
4553                   {
4554                     ((AnnotationColourGradient) cs)
4555                             .setSeqAssociated(viewAnnColour.isPerSequence());
4556                   }
4557                   if (viewAnnColour.hasPredefinedColours())
4558                   {
4559                     ((AnnotationColourGradient) cs)
4560                             .setPredefinedColours(viewAnnColour
4561                                     .isPredefinedColours());
4562                   }
4563                 }
4564               }
4565
4566             }
4567           }
4568
4569           break;
4570         }
4571
4572       }
4573     }
4574     return cs;
4575   }
4576
4577   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
4578           List<JvAnnotRow> autoAlan)
4579   {
4580     // copy over visualization settings for autocalculated annotation in the
4581     // view
4582     if (al.getAlignmentAnnotation() != null)
4583     {
4584       /**
4585        * Kludge for magic autoannotation names (see JAL-811)
4586        */
4587       String[] magicNames = new String[] { "Consensus", "Quality",
4588           "Conservation" };
4589       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4590       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4591       for (String nm : magicNames)
4592       {
4593         visan.put(nm, nullAnnot);
4594       }
4595       for (JvAnnotRow auan : autoAlan)
4596       {
4597         visan.put(auan.template.label
4598                 + (auan.template.getCalcId() == null ? "" : "\t"
4599                         + auan.template.getCalcId()), auan);
4600       }
4601       int hSize = al.getAlignmentAnnotation().length;
4602       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4603       // work through any autoCalculated annotation already on the view
4604       // removing it if it should be placed in a different location on the
4605       // annotation panel.
4606       List<String> remains = new ArrayList<String>(visan.keySet());
4607       for (int h = 0; h < hSize; h++)
4608       {
4609         jalview.datamodel.AlignmentAnnotation jalan = al
4610                 .getAlignmentAnnotation()[h];
4611         if (jalan.autoCalculated)
4612         {
4613           String k;
4614           JvAnnotRow valan = visan.get(k = jalan.label);
4615           if (jalan.getCalcId() != null)
4616           {
4617             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4618           }
4619
4620           if (valan != null)
4621           {
4622             // delete the auto calculated row from the alignment
4623             al.deleteAnnotation(jalan, false);
4624             remains.remove(k);
4625             hSize--;
4626             h--;
4627             if (valan != nullAnnot)
4628             {
4629               if (jalan != valan.template)
4630               {
4631                 // newly created autoannotation row instance
4632                 // so keep a reference to the visible annotation row
4633                 // and copy over all relevant attributes
4634                 if (valan.template.graphHeight >= 0)
4635
4636                 {
4637                   jalan.graphHeight = valan.template.graphHeight;
4638                 }
4639                 jalan.visible = valan.template.visible;
4640               }
4641               reorder.add(new JvAnnotRow(valan.order, jalan));
4642             }
4643           }
4644         }
4645       }
4646       // Add any (possibly stale) autocalculated rows that were not appended to
4647       // the view during construction
4648       for (String other : remains)
4649       {
4650         JvAnnotRow othera = visan.get(other);
4651         if (othera != nullAnnot && othera.template.getCalcId() != null
4652                 && othera.template.getCalcId().length() > 0)
4653         {
4654           reorder.add(othera);
4655         }
4656       }
4657       // now put the automatic annotation in its correct place
4658       int s = 0, srt[] = new int[reorder.size()];
4659       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4660       for (JvAnnotRow jvar : reorder)
4661       {
4662         rws[s] = jvar;
4663         srt[s++] = jvar.order;
4664       }
4665       reorder.clear();
4666       jalview.util.QuickSort.sort(srt, rws);
4667       // and re-insert the annotation at its correct position
4668       for (JvAnnotRow jvar : rws)
4669       {
4670         al.addAnnotation(jvar.template, jvar.order);
4671       }
4672       af.alignPanel.adjustAnnotationHeight();
4673     }
4674   }
4675
4676   Hashtable skipList = null;
4677
4678   /**
4679    * TODO remove this method
4680    * 
4681    * @param view
4682    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4683    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4684    *         throw new Error("Implementation Error. No skipList defined for this
4685    *         Jalview2XML instance."); } return (AlignFrame)
4686    *         skipList.get(view.getSequenceSetId()); }
4687    */
4688
4689   /**
4690    * Check if the Jalview view contained in object should be skipped or not.
4691    * 
4692    * @param object
4693    * @return true if view's sequenceSetId is a key in skipList
4694    */
4695   private boolean skipViewport(JalviewModel object)
4696   {
4697     if (skipList == null)
4698     {
4699       return false;
4700     }
4701     String id;
4702     if (skipList.containsKey(id = object.getJalviewModelSequence()
4703             .getViewport()[0].getSequenceSetId()))
4704     {
4705       if (Cache.log != null && Cache.log.isDebugEnabled())
4706       {
4707         Cache.log.debug("Skipping seuqence set id " + id);
4708       }
4709       return true;
4710     }
4711     return false;
4712   }
4713
4714   public void addToSkipList(AlignFrame af)
4715   {
4716     if (skipList == null)
4717     {
4718       skipList = new Hashtable();
4719     }
4720     skipList.put(af.getViewport().getSequenceSetId(), af);
4721   }
4722
4723   public void clearSkipList()
4724   {
4725     if (skipList != null)
4726     {
4727       skipList.clear();
4728       skipList = null;
4729     }
4730   }
4731
4732   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
4733           boolean ignoreUnrefed)
4734   {
4735     jalview.datamodel.AlignmentI ds = getDatasetFor(vamsasSet
4736             .getDatasetId());
4737     Vector dseqs = null;
4738     if (ds == null)
4739     {
4740       // create a list of new dataset sequences
4741       dseqs = new Vector();
4742     }
4743     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4744     {
4745       Sequence vamsasSeq = vamsasSet.getSequence(i);
4746       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4747     }
4748     // create a new dataset
4749     if (ds == null)
4750     {
4751       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4752       dseqs.copyInto(dsseqs);
4753       ds = new jalview.datamodel.Alignment(dsseqs);
4754       debug("Created new dataset " + vamsasSet.getDatasetId()
4755               + " for alignment " + System.identityHashCode(al));
4756       addDatasetRef(vamsasSet.getDatasetId(), ds);
4757     }
4758     // set the dataset for the newly imported alignment.
4759     if (al.getDataset() == null && !ignoreUnrefed)
4760     {
4761       al.setDataset(ds);
4762     }
4763   }
4764
4765   /**
4766    * 
4767    * @param vamsasSeq
4768    *          sequence definition to create/merge dataset sequence for
4769    * @param ds
4770    *          dataset alignment
4771    * @param dseqs
4772    *          vector to add new dataset sequence to
4773    */
4774   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4775           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4776   {
4777     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4778     // xRef Codon Maps
4779     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4780     SequenceI dsq = null;
4781     if (sq != null && sq.getDatasetSequence() != null)
4782     {
4783       dsq = sq.getDatasetSequence();
4784     }
4785     if (sq == null && ignoreUnrefed)
4786     {
4787       return;
4788     }
4789     String sqid = vamsasSeq.getDsseqid();
4790     if (dsq == null)
4791     {
4792       // need to create or add a new dataset sequence reference to this sequence
4793       if (sqid != null)
4794       {
4795         dsq = seqRefIds.get(sqid);
4796       }
4797       // check again
4798       if (dsq == null)
4799       {
4800         // make a new dataset sequence
4801         dsq = sq.createDatasetSequence();
4802         if (sqid == null)
4803         {
4804           // make up a new dataset reference for this sequence
4805           sqid = seqHash(dsq);
4806         }
4807         dsq.setVamsasId(uniqueSetSuffix + sqid);
4808         seqRefIds.put(sqid, dsq);
4809         if (ds == null)
4810         {
4811           if (dseqs != null)
4812           {
4813             dseqs.addElement(dsq);
4814           }
4815         }
4816         else
4817         {
4818           ds.addSequence(dsq);
4819         }
4820       }
4821       else
4822       {
4823         if (sq != dsq)
4824         { // make this dataset sequence sq's dataset sequence
4825           sq.setDatasetSequence(dsq);
4826           // and update the current dataset alignment
4827           if (ds == null)
4828           {
4829             if (dseqs != null)
4830             {
4831               if (!dseqs.contains(dsq))
4832               {
4833                 dseqs.add(dsq);
4834               }
4835             }
4836             else
4837             {
4838               if (ds.findIndex(dsq) < 0)
4839               {
4840                 ds.addSequence(dsq);
4841               }
4842             }
4843           }
4844         }
4845       }
4846     }
4847     // TODO: refactor this as a merge dataset sequence function
4848     // now check that sq (the dataset sequence) sequence really is the union of
4849     // all references to it
4850     // boolean pre = sq.getStart() < dsq.getStart();
4851     // boolean post = sq.getEnd() > dsq.getEnd();
4852     // if (pre || post)
4853     if (sq != dsq)
4854     {
4855       // StringBuffer sb = new StringBuffer();
4856       String newres = jalview.analysis.AlignSeq.extractGaps(
4857               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4858       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4859               && newres.length() > dsq.getLength())
4860       {
4861         // Update with the longer sequence.
4862         synchronized (dsq)
4863         {
4864           /*
4865            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4866            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4867            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4868            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4869            */
4870           dsq.setSequence(newres);
4871         }
4872         // TODO: merges will never happen if we 'know' we have the real dataset
4873         // sequence - this should be detected when id==dssid
4874         System.err
4875                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4876         // + (pre ? "prepended" : "") + " "
4877         // + (post ? "appended" : ""));
4878       }
4879     }
4880   }
4881
4882   /*
4883    * TODO use AlignmentI here and in related methods - needs
4884    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
4885    */
4886   Hashtable<String, AlignmentI> datasetIds = null;
4887
4888   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
4889
4890   private AlignmentI getDatasetFor(String datasetId)
4891   {
4892     if (datasetIds == null)
4893     {
4894       datasetIds = new Hashtable<String, AlignmentI>();
4895       return null;
4896     }
4897     if (datasetIds.containsKey(datasetId))
4898     {
4899       return datasetIds.get(datasetId);
4900     }
4901     return null;
4902   }
4903
4904   private void addDatasetRef(String datasetId, AlignmentI dataset)
4905   {
4906     if (datasetIds == null)
4907     {
4908       datasetIds = new Hashtable<String, AlignmentI>();
4909     }
4910     datasetIds.put(datasetId, dataset);
4911   }
4912
4913   /**
4914    * make a new dataset ID for this jalview dataset alignment
4915    * 
4916    * @param dataset
4917    * @return
4918    */
4919   private String getDatasetIdRef(AlignmentI dataset)
4920   {
4921     if (dataset.getDataset() != null)
4922     {
4923       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4924     }
4925     String datasetId = makeHashCode(dataset, null);
4926     if (datasetId == null)
4927     {
4928       // make a new datasetId and record it
4929       if (dataset2Ids == null)
4930       {
4931         dataset2Ids = new IdentityHashMap<AlignmentI, String>();
4932       }
4933       else
4934       {
4935         datasetId = dataset2Ids.get(dataset);
4936       }
4937       if (datasetId == null)
4938       {
4939         datasetId = "ds" + dataset2Ids.size() + 1;
4940         dataset2Ids.put(dataset, datasetId);
4941       }
4942     }
4943     return datasetId;
4944   }
4945
4946   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4947   {
4948     for (int d = 0; d < sequence.getDBRefCount(); d++)
4949     {
4950       DBRef dr = sequence.getDBRef(d);
4951       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4952               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4953                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4954       if (dr.getMapping() != null)
4955       {
4956         entry.setMap(addMapping(dr.getMapping()));
4957       }
4958       datasetSequence.addDBRef(entry);
4959     }
4960   }
4961
4962   private jalview.datamodel.Mapping addMapping(Mapping m)
4963   {
4964     SequenceI dsto = null;
4965     // Mapping m = dr.getMapping();
4966     int fr[] = new int[m.getMapListFromCount() * 2];
4967     Enumeration f = m.enumerateMapListFrom();
4968     for (int _i = 0; f.hasMoreElements(); _i += 2)
4969     {
4970       MapListFrom mf = (MapListFrom) f.nextElement();
4971       fr[_i] = mf.getStart();
4972       fr[_i + 1] = mf.getEnd();
4973     }
4974     int fto[] = new int[m.getMapListToCount() * 2];
4975     f = m.enumerateMapListTo();
4976     for (int _i = 0; f.hasMoreElements(); _i += 2)
4977     {
4978       MapListTo mf = (MapListTo) f.nextElement();
4979       fto[_i] = mf.getStart();
4980       fto[_i + 1] = mf.getEnd();
4981     }
4982     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
4983             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
4984     if (m.getMappingChoice() != null)
4985     {
4986       MappingChoice mc = m.getMappingChoice();
4987       if (mc.getDseqFor() != null)
4988       {
4989         String dsfor = "" + mc.getDseqFor();
4990         if (seqRefIds.containsKey(dsfor))
4991         {
4992           /**
4993            * recover from hash
4994            */
4995           jmap.setTo(seqRefIds.get(dsfor));
4996         }
4997         else
4998         {
4999           frefedSequence.add(new Object[] { dsfor, jmap });
5000         }
5001       }
5002       else
5003       {
5004         /**
5005          * local sequence definition
5006          */
5007         Sequence ms = mc.getSequence();
5008         SequenceI djs = null;
5009         String sqid = ms.getDsseqid();
5010         if (sqid != null && sqid.length() > 0)
5011         {
5012           /*
5013            * recover dataset sequence
5014            */
5015           djs = seqRefIds.get(sqid);
5016         }
5017         else
5018         {
5019           System.err
5020                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5021           sqid = ((Object) ms).toString(); // make up a new hascode for
5022           // undefined dataset sequence hash
5023           // (unlikely to happen)
5024         }
5025
5026         if (djs == null)
5027         {
5028           /**
5029            * make a new dataset sequence and add it to refIds hash
5030            */
5031           djs = new jalview.datamodel.Sequence(ms.getName(),
5032                   ms.getSequence());
5033           djs.setStart(jmap.getMap().getToLowest());
5034           djs.setEnd(jmap.getMap().getToHighest());
5035           djs.setVamsasId(uniqueSetSuffix + sqid);
5036           jmap.setTo(djs);
5037           seqRefIds.put(sqid, djs);
5038
5039         }
5040         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5041         addDBRefs(djs, ms);
5042
5043       }
5044     }
5045     return (jmap);
5046
5047   }
5048
5049   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5050           boolean keepSeqRefs)
5051   {
5052     initSeqRefs();
5053     JalviewModel jm = saveState(ap, null, null, null);
5054
5055     if (!keepSeqRefs)
5056     {
5057       clearSeqRefs();
5058       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5059     }
5060     else
5061     {
5062       uniqueSetSuffix = "";
5063       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5064       // overwrite the
5065       // view we just
5066       // copied
5067     }
5068     if (this.frefedSequence == null)
5069     {
5070       frefedSequence = new Vector();
5071     }
5072
5073     viewportsAdded.clear();
5074
5075     AlignFrame af = loadFromObject(jm, null, false, null);
5076     af.alignPanels.clear();
5077     af.closeMenuItem_actionPerformed(true);
5078
5079     /*
5080      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5081      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5082      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5083      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5084      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5085      */
5086
5087     return af.alignPanel;
5088   }
5089
5090   /**
5091    * flag indicating if hashtables should be cleared on finalization TODO this
5092    * flag may not be necessary
5093    */
5094   private final boolean _cleartables = true;
5095
5096   private Hashtable jvids2vobj;
5097
5098   /*
5099    * (non-Javadoc)
5100    * 
5101    * @see java.lang.Object#finalize()
5102    */
5103   @Override
5104   protected void finalize() throws Throwable
5105   {
5106     // really make sure we have no buried refs left.
5107     if (_cleartables)
5108     {
5109       clearSeqRefs();
5110     }
5111     this.seqRefIds = null;
5112     this.seqsToIds = null;
5113     super.finalize();
5114   }
5115
5116   private void warn(String msg)
5117   {
5118     warn(msg, null);
5119   }
5120
5121   private void warn(String msg, Exception e)
5122   {
5123     if (Cache.log != null)
5124     {
5125       if (e != null)
5126       {
5127         Cache.log.warn(msg, e);
5128       }
5129       else
5130       {
5131         Cache.log.warn(msg);
5132       }
5133     }
5134     else
5135     {
5136       System.err.println("Warning: " + msg);
5137       if (e != null)
5138       {
5139         e.printStackTrace();
5140       }
5141     }
5142   }
5143
5144   private void debug(String string)
5145   {
5146     debug(string, null);
5147   }
5148
5149   private void debug(String msg, Exception e)
5150   {
5151     if (Cache.log != null)
5152     {
5153       if (e != null)
5154       {
5155         Cache.log.debug(msg, e);
5156       }
5157       else
5158       {
5159         Cache.log.debug(msg);
5160       }
5161     }
5162     else
5163     {
5164       System.err.println("Warning: " + msg);
5165       if (e != null)
5166       {
5167         e.printStackTrace();
5168       }
5169     }
5170   }
5171
5172   /**
5173    * set the object to ID mapping tables used to write/recover objects and XML
5174    * ID strings for the jalview project. If external tables are provided then
5175    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5176    * object goes out of scope. - also populates the datasetIds hashtable with
5177    * alignment objects containing dataset sequences
5178    * 
5179    * @param vobj2jv
5180    *          Map from ID strings to jalview datamodel
5181    * @param jv2vobj
5182    *          Map from jalview datamodel to ID strings
5183    * 
5184    * 
5185    */
5186   public void setObjectMappingTables(Hashtable vobj2jv,
5187           IdentityHashMap jv2vobj)
5188   {
5189     this.jv2vobj = jv2vobj;
5190     this.vobj2jv = vobj2jv;
5191     Iterator ds = jv2vobj.keySet().iterator();
5192     String id;
5193     while (ds.hasNext())
5194     {
5195       Object jvobj = ds.next();
5196       id = jv2vobj.get(jvobj).toString();
5197       if (jvobj instanceof jalview.datamodel.Alignment)
5198       {
5199         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5200         {
5201           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5202         }
5203       }
5204       else if (jvobj instanceof jalview.datamodel.Sequence)
5205       {
5206         // register sequence object so the XML parser can recover it.
5207         if (seqRefIds == null)
5208         {
5209           seqRefIds = new HashMap<String, SequenceI>();
5210         }
5211         if (seqsToIds == null)
5212         {
5213           seqsToIds = new IdentityHashMap<SequenceI, String>();
5214         }
5215         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5216         seqsToIds.put((SequenceI) jvobj, id);
5217       }
5218       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5219       {
5220         String anid;
5221         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5222         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5223         if (jvann.annotationId == null)
5224         {
5225           jvann.annotationId = anid;
5226         }
5227         if (!jvann.annotationId.equals(anid))
5228         {
5229           // TODO verify that this is the correct behaviour
5230           this.warn("Overriding Annotation ID for " + anid
5231                   + " from different id : " + jvann.annotationId);
5232           jvann.annotationId = anid;
5233         }
5234       }
5235       else if (jvobj instanceof String)
5236       {
5237         if (jvids2vobj == null)
5238         {
5239           jvids2vobj = new Hashtable();
5240           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5241         }
5242       }
5243       else
5244       {
5245         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5246       }
5247     }
5248   }
5249
5250   /**
5251    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5252    * objects created from the project archive. If string is null (default for
5253    * construction) then suffix will be set automatically.
5254    * 
5255    * @param string
5256    */
5257   public void setUniqueSetSuffix(String string)
5258   {
5259     uniqueSetSuffix = string;
5260
5261   }
5262
5263   /**
5264    * uses skipList2 as the skipList for skipping views on sequence sets
5265    * associated with keys in the skipList
5266    * 
5267    * @param skipList2
5268    */
5269   public void setSkipList(Hashtable skipList2)
5270   {
5271     skipList = skipList2;
5272   }
5273
5274   /**
5275    * Reads the jar entry of given name and returns its contents, or null if the
5276    * entry is not found.
5277    * 
5278    * @param jprovider
5279    * @param jarEntryName
5280    * @return
5281    */
5282   protected String readJarEntry(jarInputStreamProvider jprovider,
5283           String jarEntryName)
5284   {
5285     String result = null;
5286     BufferedReader in = null;
5287
5288     try
5289     {
5290       /*
5291        * Reopen the jar input stream and traverse its entries to find a matching
5292        * name
5293        */
5294       JarInputStream jin = jprovider.getJarInputStream();
5295       JarEntry entry = null;
5296       do
5297       {
5298         entry = jin.getNextJarEntry();
5299       } while (entry != null && !entry.getName().equals(jarEntryName));
5300
5301       if (entry != null)
5302       {
5303         StringBuilder out = new StringBuilder(256);
5304         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5305         String data;
5306
5307         while ((data = in.readLine()) != null)
5308         {
5309           out.append(data);
5310         }
5311         result = out.toString();
5312       }
5313       else
5314       {
5315         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5316       }
5317     } catch (Exception ex)
5318     {
5319       ex.printStackTrace();
5320     } finally
5321     {
5322       if (in != null)
5323       {
5324         try
5325         {
5326           in.close();
5327         } catch (IOException e)
5328         {
5329           // ignore
5330         }
5331       }
5332     }
5333
5334     return result;
5335   }
5336
5337   /**
5338    * Returns an incrementing counter (0, 1, 2...)
5339    * 
5340    * @return
5341    */
5342   private synchronized int nextCounter()
5343   {
5344     return counter++;
5345   }
5346 }