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