JAL-2738 copy to spikes/mungo
[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<>();
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<>();
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<>();
252     }
253     if (seqRefIds == null)
254     {
255       seqRefIds = new HashMap<>();
256     }
257     if (incompleteSeqs == null)
258     {
259       incompleteSeqs = new HashMap<>();
260     }
261     if (frefedSequence == null)
262     {
263       frefedSequence = new ArrayList<>();
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,
365           final jalview.datamodel.Mapping _jmap)
366   {
367
368     SeqFref fref = new SeqFref(sref, "Codon Frame")
369     {
370       AlignedCodonFrame cf = _cf;
371
372       public jalview.datamodel.Mapping mp = _jmap;
373
374       @Override
375       public boolean isResolvable()
376       {
377         return super.isResolvable() && mp.getTo() != null;
378       };
379
380       @Override
381       boolean resolve()
382       {
383         SequenceI seq = getSrefDatasetSeq();
384         if (seq == null)
385         {
386           return false;
387         }
388         cf.addMap(seq, mp.getTo(), mp.getMap());
389         return true;
390       }
391     };
392     return fref;
393   }
394
395   public void resolveFrefedSequences()
396   {
397     Iterator<SeqFref> nextFref = frefedSequence.iterator();
398     int toresolve = frefedSequence.size();
399     int unresolved = 0, failedtoresolve = 0;
400     while (nextFref.hasNext())
401     {
402       SeqFref ref = nextFref.next();
403       if (ref.isResolvable())
404       {
405         try
406         {
407           if (ref.resolve())
408           {
409             nextFref.remove();
410           }
411           else
412           {
413             failedtoresolve++;
414           }
415         } catch (Exception x)
416         {
417           System.err.println(
418                   "IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
419                           + ref.getSref());
420           x.printStackTrace();
421           failedtoresolve++;
422         }
423       }
424       else
425       {
426         unresolved++;
427       }
428     }
429     if (unresolved > 0)
430     {
431       System.err.println("Jalview Project Import: There were " + unresolved
432               + " forward references left unresolved on the stack.");
433     }
434     if (failedtoresolve > 0)
435     {
436       System.err.println("SERIOUS! " + failedtoresolve
437               + " resolvable forward references failed to resolve.");
438     }
439     if (incompleteSeqs != null && incompleteSeqs.size() > 0)
440     {
441       System.err.println(
442               "Jalview Project Import: There are " + incompleteSeqs.size()
443                       + " sequences which may have incomplete metadata.");
444       if (incompleteSeqs.size() < 10)
445       {
446         for (SequenceI s : incompleteSeqs.values())
447         {
448           System.err.println(s.toString());
449         }
450       }
451       else
452       {
453         System.err.println(
454                 "Too many to report. Skipping output of incomplete sequences.");
455       }
456     }
457   }
458
459   /**
460    * This maintains a map of viewports, the key being the seqSetId. Important to
461    * set historyItem and redoList for multiple views
462    */
463   Map<String, AlignViewport> viewportsAdded = new HashMap<>();
464
465   Map<String, AlignmentAnnotation> annotationIds = new HashMap<>();
466
467   String uniqueSetSuffix = "";
468
469   /**
470    * List of pdbfiles added to Jar
471    */
472   List<String> pdbfiles = null;
473
474   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
475   public void saveState(File statefile)
476   {
477     FileOutputStream fos = null;
478     try
479     {
480       fos = new FileOutputStream(statefile);
481       JarOutputStream jout = new JarOutputStream(fos);
482       saveState(jout);
483
484     } catch (Exception e)
485     {
486       // TODO: inform user of the problem - they need to know if their data was
487       // not saved !
488       if (errorMessage == null)
489       {
490         errorMessage = "Couldn't write Jalview Archive to output file '"
491                 + statefile + "' - See console error log for details";
492       }
493       else
494       {
495         errorMessage += "(output file was '" + statefile + "')";
496       }
497       e.printStackTrace();
498     } finally
499     {
500       if (fos != null)
501       {
502         try
503         {
504           fos.close();
505         } catch (IOException e)
506         {
507           // ignore
508         }
509       }
510     }
511     reportErrors();
512   }
513
514   /**
515    * Writes a jalview project archive to the given Jar output stream.
516    * 
517    * @param jout
518    */
519   public void saveState(JarOutputStream jout)
520   {
521     AlignFrame[] frames = Desktop.getAlignFrames();
522
523     if (frames == null)
524     {
525       return;
526     }
527     saveAllFrames(Arrays.asList(frames), jout);
528   }
529
530   /**
531    * core method for storing state for a set of AlignFrames.
532    * 
533    * @param frames
534    *          - frames involving all data to be exported (including containing
535    *          splitframes)
536    * @param jout
537    *          - project output stream
538    */
539   private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
540   {
541     Hashtable<String, AlignFrame> dsses = new Hashtable<>();
542
543     /*
544      * ensure cached data is clear before starting
545      */
546     // todo tidy up seqRefIds, seqsToIds initialisation / reset
547     rnaSessions.clear();
548     splitFrameCandidates.clear();
549
550     try
551     {
552
553       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
554       // //////////////////////////////////////////////////
555
556       List<String> shortNames = new ArrayList<>();
557       List<String> viewIds = new ArrayList<>();
558
559       // REVERSE ORDER
560       for (int i = frames.size() - 1; i > -1; i--)
561       {
562         AlignFrame af = frames.get(i);
563         // skip ?
564         if (skipList != null && 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(
586                   af.getViewport().getAlignment().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
633               .substring(shortName.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<>();
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<>();
753     }
754
755     initSeqRefs();
756
757     List<UserColourScheme> userColours = new ArrayList<>();
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(
767             jalview.bin.Cache.getDefault("VERSION", "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<>();
812     // record the set of vamsas sequence XML POJO we create.
813     HashMap<String, Sequence> vamsasSetIds = new HashMap<>();
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(
861                   av.getAlignment().getHiddenSequences().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, viewIds,
952                       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(
969                           "Error saving viewer state: " + 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<>();
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(
1042                     createVamsasMapping(pmaps[m], dnas[m], null, 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<>();
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.getGroupColourScheme();
1180           if (groupColourScheme.conservationApplied())
1181           {
1182             jGroup.setConsThreshold(groupColourScheme.getConservationInc());
1183
1184             if (colourScheme instanceof jalview.schemes.UserColourScheme)
1185             {
1186               jGroup.setColour(
1187                       setUserColourScheme(colourScheme, userColours, jms));
1188             }
1189             else
1190             {
1191               jGroup.setColour(colourScheme.getSchemeName());
1192             }
1193           }
1194           else if (colourScheme instanceof jalview.schemes.AnnotationColourGradient)
1195           {
1196             jGroup.setColour("AnnotationColourGradient");
1197             jGroup.setAnnotationColours(constructAnnotationColours(
1198                     (jalview.schemes.AnnotationColourGradient) colourScheme,
1199                     userColours, jms));
1200           }
1201           else if (colourScheme instanceof jalview.schemes.UserColourScheme)
1202           {
1203             jGroup.setColour(
1204                     setUserColourScheme(colourScheme, userColours, jms));
1205           }
1206           else
1207           {
1208             jGroup.setColour(colourScheme.getSchemeName());
1209           }
1210
1211           jGroup.setPidThreshold(groupColourScheme.getThreshold());
1212         }
1213
1214         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1215         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1216         jGroup.setDisplayText(sg.getDisplayText());
1217         jGroup.setColourText(sg.getColourText());
1218         jGroup.setTextCol1(sg.textColour.getRGB());
1219         jGroup.setTextCol2(sg.textColour2.getRGB());
1220         jGroup.setTextColThreshold(sg.thresholdTextColour);
1221         jGroup.setShowUnconserved(sg.getShowNonconserved());
1222         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1223         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1224         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1225         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1226         for (SequenceI seq : sg.getSequences())
1227         {
1228           jGroup.addSeq(seqHash(seq));
1229         }
1230       }
1231
1232       jms.setJGroup(groups);
1233     }
1234     if (!storeDS)
1235     {
1236       // /////////SAVE VIEWPORT
1237       Viewport view = new Viewport();
1238       view.setTitle(ap.alignFrame.getTitle());
1239       view.setSequenceSetId(
1240               makeHashCode(av.getSequenceSetId(), av.getSequenceSetId()));
1241       view.setId(av.getViewId());
1242       if (av.getCodingComplement() != null)
1243       {
1244         view.setComplementId(av.getCodingComplement().getViewId());
1245       }
1246       view.setViewName(av.viewName);
1247       view.setGatheredViews(av.isGatherViewsHere());
1248
1249       Rectangle size = ap.av.getExplodedGeometry();
1250       Rectangle position = size;
1251       if (size == null)
1252       {
1253         size = ap.alignFrame.getBounds();
1254         if (av.getCodingComplement() != null)
1255         {
1256           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
1257                   .getBounds();
1258         }
1259         else
1260         {
1261           position = size;
1262         }
1263       }
1264       view.setXpos(position.x);
1265       view.setYpos(position.y);
1266
1267       view.setWidth(size.width);
1268       view.setHeight(size.height);
1269
1270       view.setStartRes(vpRanges.getStartRes());
1271       view.setStartSeq(vpRanges.getStartSeq());
1272
1273       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1274       {
1275         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1276                 userColours, jms));
1277       }
1278       else if (av
1279               .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
1292                 .getColourName(av.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<>();
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
1368                       : (fcol.isBelowThreshold() ? 0 : -1));
1369             }
1370             else
1371             {
1372               setting.setColour(fcol.getColour().getRGB());
1373             }
1374
1375             setting.setDisplay(
1376                     av.getFeaturesDisplayed().isVisible(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<>();
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)
1415         {
1416           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1417         }
1418         else
1419         {
1420           ArrayList<int[]> hiddenRegions = hidden.getHiddenColumnsCopy();
1421           for (int[] region : hiddenRegions)
1422           {
1423             HiddenColumns hc = new HiddenColumns();
1424             hc.setStart(region[0]);
1425             hc.setEnd(region[1]);
1426             view.addHiddenColumns(hc);
1427           }
1428         }
1429       }
1430       if (calcIdSet.size() > 0)
1431       {
1432         for (String calcId : calcIdSet)
1433         {
1434           if (calcId.trim().length() > 0)
1435           {
1436             CalcIdParam cidp = createCalcIdParam(calcId, av);
1437             // Some calcIds have no parameters.
1438             if (cidp != null)
1439             {
1440               view.addCalcIdParam(cidp);
1441             }
1442           }
1443         }
1444       }
1445
1446       jms.addViewport(view);
1447     }
1448     object.setJalviewModelSequence(jms);
1449     object.getVamsasModel().addSequenceSet(vamsasSet);
1450
1451     if (jout != null && fileName != null)
1452     {
1453       // We may not want to write the object to disk,
1454       // eg we can copy the alignViewport to a new view object
1455       // using save and then load
1456       try
1457       {
1458         System.out.println("Writing jar entry " + fileName);
1459         JarEntry entry = new JarEntry(fileName);
1460         jout.putNextEntry(entry);
1461         PrintWriter pout = new PrintWriter(
1462                 new OutputStreamWriter(jout, UTF_8));
1463         Marshaller marshaller = new Marshaller(pout);
1464         marshaller.marshal(object);
1465         pout.flush();
1466         jout.closeEntry();
1467       } catch (Exception ex)
1468       {
1469         // TODO: raise error in GUI if marshalling failed.
1470         ex.printStackTrace();
1471       }
1472     }
1473     return object;
1474   }
1475
1476   /**
1477    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1478    * for each viewer, with
1479    * <ul>
1480    * <li>viewer geometry (position, size, split pane divider location)</li>
1481    * <li>index of the selected structure in the viewer (currently shows gapped
1482    * or ungapped)</li>
1483    * <li>the id of the annotation holding RNA secondary structure</li>
1484    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1485    * </ul>
1486    * Varna viewer state is also written out (in native Varna XML) to separate
1487    * project jar entries. A separate entry is written for each RNA structure
1488    * displayed, with the naming convention
1489    * <ul>
1490    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1491    * </ul>
1492    * 
1493    * @param jout
1494    * @param jseq
1495    * @param jds
1496    * @param viewIds
1497    * @param ap
1498    * @param storeDataset
1499    */
1500   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1501           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1502           boolean storeDataset)
1503   {
1504     if (Desktop.desktop == null)
1505     {
1506       return;
1507     }
1508     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1509     for (int f = frames.length - 1; f > -1; f--)
1510     {
1511       if (frames[f] instanceof AppVarna)
1512       {
1513         AppVarna varna = (AppVarna) frames[f];
1514         /*
1515          * link the sequence to every viewer that is showing it and is linked to
1516          * its alignment panel
1517          */
1518         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1519         {
1520           String viewId = varna.getViewId();
1521           RnaViewer rna = new RnaViewer();
1522           rna.setViewId(viewId);
1523           rna.setTitle(varna.getTitle());
1524           rna.setXpos(varna.getX());
1525           rna.setYpos(varna.getY());
1526           rna.setWidth(varna.getWidth());
1527           rna.setHeight(varna.getHeight());
1528           rna.setDividerLocation(varna.getDividerLocation());
1529           rna.setSelectedRna(varna.getSelectedIndex());
1530           jseq.addRnaViewer(rna);
1531
1532           /*
1533            * Store each Varna panel's state once in the project per sequence.
1534            * First time through only (storeDataset==false)
1535            */
1536           // boolean storeSessions = false;
1537           // String sequenceViewId = viewId + seqsToIds.get(jds);
1538           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1539           // {
1540           // viewIds.add(sequenceViewId);
1541           // storeSessions = true;
1542           // }
1543           for (RnaModel model : varna.getModels())
1544           {
1545             if (model.seq == jds)
1546             {
1547               /*
1548                * VARNA saves each view (sequence or alignment secondary
1549                * structure, gapped or trimmed) as a separate XML file
1550                */
1551               String jarEntryName = rnaSessions.get(model);
1552               if (jarEntryName == null)
1553               {
1554
1555                 String varnaStateFile = varna.getStateInfo(model.rna);
1556                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1557                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1558                 rnaSessions.put(model, jarEntryName);
1559               }
1560               SecondaryStructure ss = new SecondaryStructure();
1561               String annotationId = varna.getAnnotation(jds).annotationId;
1562               ss.setAnnotationId(annotationId);
1563               ss.setViewerState(jarEntryName);
1564               ss.setGapped(model.gapped);
1565               ss.setTitle(model.title);
1566               rna.addSecondaryStructure(ss);
1567             }
1568           }
1569         }
1570       }
1571     }
1572   }
1573
1574   /**
1575    * Copy the contents of a file to a new entry added to the output jar
1576    * 
1577    * @param jout
1578    * @param infilePath
1579    * @param jarEntryName
1580    */
1581   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1582           String jarEntryName)
1583   {
1584     DataInputStream dis = null;
1585     try
1586     {
1587       File file = new File(infilePath);
1588       if (file.exists() && jout != null)
1589       {
1590         dis = new DataInputStream(new FileInputStream(file));
1591         byte[] data = new byte[(int) file.length()];
1592         dis.readFully(data);
1593         writeJarEntry(jout, jarEntryName, data);
1594       }
1595     } catch (Exception ex)
1596     {
1597       ex.printStackTrace();
1598     } finally
1599     {
1600       if (dis != null)
1601       {
1602         try
1603         {
1604           dis.close();
1605         } catch (IOException e)
1606         {
1607           // ignore
1608         }
1609       }
1610     }
1611   }
1612
1613   /**
1614    * Write the data to a new entry of given name in the output jar file
1615    * 
1616    * @param jout
1617    * @param jarEntryName
1618    * @param data
1619    * @throws IOException
1620    */
1621   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
1622           byte[] data) throws IOException
1623   {
1624     if (jout != null)
1625     {
1626       System.out.println("Writing jar entry " + jarEntryName);
1627       jout.putNextEntry(new JarEntry(jarEntryName));
1628       DataOutputStream dout = new DataOutputStream(jout);
1629       dout.write(data, 0, data.length);
1630       dout.flush();
1631       jout.closeEntry();
1632     }
1633   }
1634
1635   /**
1636    * Save the state of a structure viewer
1637    * 
1638    * @param ap
1639    * @param jds
1640    * @param pdb
1641    *          the archive XML element under which to save the state
1642    * @param entry
1643    * @param viewIds
1644    * @param matchedFile
1645    * @param viewFrame
1646    * @return
1647    */
1648   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
1649           Pdbids pdb, PDBEntry entry, List<String> viewIds,
1650           String matchedFile, StructureViewerBase viewFrame)
1651   {
1652     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
1653
1654     /*
1655      * Look for any bindings for this viewer to the PDB file of interest
1656      * (including part matches excluding chain id)
1657      */
1658     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
1659     {
1660       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
1661       final String pdbId = pdbentry.getId();
1662       if (!pdbId.equals(entry.getId())
1663               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
1664                       .startsWith(pdbId.toLowerCase())))
1665       {
1666         /*
1667          * not interested in a binding to a different PDB entry here
1668          */
1669         continue;
1670       }
1671       if (matchedFile == null)
1672       {
1673         matchedFile = pdbentry.getFile();
1674       }
1675       else if (!matchedFile.equals(pdbentry.getFile()))
1676       {
1677         Cache.log.warn(
1678                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
1679                         + pdbentry.getFile());
1680       }
1681       // record the
1682       // file so we
1683       // can get at it if the ID
1684       // match is ambiguous (e.g.
1685       // 1QIP==1qipA)
1686
1687       for (int smap = 0; smap < viewFrame.getBinding()
1688               .getSequence()[peid].length; smap++)
1689       {
1690         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
1691         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
1692         {
1693           StructureState state = new StructureState();
1694           state.setVisible(true);
1695           state.setXpos(viewFrame.getX());
1696           state.setYpos(viewFrame.getY());
1697           state.setWidth(viewFrame.getWidth());
1698           state.setHeight(viewFrame.getHeight());
1699           final String viewId = viewFrame.getViewId();
1700           state.setViewId(viewId);
1701           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
1702           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
1703           state.setColourByJmol(viewFrame.isColouredByViewer());
1704           state.setType(viewFrame.getViewerType().toString());
1705           pdb.addStructureState(state);
1706         }
1707       }
1708     }
1709     return matchedFile;
1710   }
1711
1712   /**
1713    * Populates the AnnotationColours xml for save. This captures the settings of
1714    * the options in the 'Colour by Annotation' dialog.
1715    * 
1716    * @param acg
1717    * @param userColours
1718    * @param jms
1719    * @return
1720    */
1721   private AnnotationColours constructAnnotationColours(
1722           AnnotationColourGradient acg, List<UserColourScheme> userColours,
1723           JalviewModelSequence jms)
1724   {
1725     AnnotationColours ac = new AnnotationColours();
1726     ac.setAboveThreshold(acg.getAboveThreshold());
1727     ac.setThreshold(acg.getAnnotationThreshold());
1728     // 2.10.2 save annotationId (unique) not annotation label
1729     ac.setAnnotation(acg.getAnnotation().annotationId);
1730     if (acg.getBaseColour() instanceof UserColourScheme)
1731     {
1732       ac.setColourScheme(
1733               setUserColourScheme(acg.getBaseColour(), userColours, jms));
1734     }
1735     else
1736     {
1737       ac.setColourScheme(
1738               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(annotation.groupRef,
1782                   groupIdr = ("" + System.currentTimeMillis()
1783                           + annotation.groupRef.getName()
1784                           + groupRefs.size()));
1785         }
1786         an.setGroupRef(groupIdr.toString());
1787       }
1788
1789       // store all visualization attributes for annotation
1790       an.setGraphHeight(annotation.graphHeight);
1791       an.setCentreColLabels(annotation.centreColLabels);
1792       an.setScaleColLabels(annotation.scaleColLabel);
1793       an.setShowAllColLabels(annotation.showAllColLabels);
1794       an.setBelowAlignment(annotation.belowAlignment);
1795
1796       if (annotation.graph > 0)
1797       {
1798         an.setGraph(true);
1799         an.setGraphType(annotation.graph);
1800         an.setGraphGroup(annotation.graphGroup);
1801         if (annotation.getThreshold() != null)
1802         {
1803           ThresholdLine line = new ThresholdLine();
1804           line.setLabel(annotation.getThreshold().label);
1805           line.setValue(annotation.getThreshold().value);
1806           line.setColour(annotation.getThreshold().colour.getRGB());
1807           an.setThresholdLine(line);
1808         }
1809       }
1810       else
1811       {
1812         an.setGraph(false);
1813       }
1814
1815       an.setLabel(annotation.label);
1816
1817       if (annotation == av.getAlignmentQualityAnnot()
1818               || annotation == av.getAlignmentConservationAnnotation()
1819               || annotation == av.getAlignmentConsensusAnnotation()
1820               || annotation.autoCalculated)
1821       {
1822         // new way of indicating autocalculated annotation -
1823         an.setAutoCalculated(annotation.autoCalculated);
1824       }
1825       if (annotation.hasScore())
1826       {
1827         an.setScore(annotation.getScore());
1828       }
1829
1830       if (annotation.getCalcId() != null)
1831       {
1832         calcIdSet.add(annotation.getCalcId());
1833         an.setCalcId(annotation.getCalcId());
1834       }
1835       if (annotation.hasProperties())
1836       {
1837         for (String pr : annotation.getProperties())
1838         {
1839           Property prop = new Property();
1840           prop.setName(pr);
1841           prop.setValue(annotation.getProperty(pr));
1842           an.addProperty(prop);
1843         }
1844       }
1845
1846       AnnotationElement ae;
1847       if (annotation.annotations != null)
1848       {
1849         an.setScoreOnly(false);
1850         for (int a = 0; a < annotation.annotations.length; a++)
1851         {
1852           if ((annotation == null) || (annotation.annotations[a] == null))
1853           {
1854             continue;
1855           }
1856
1857           ae = new AnnotationElement();
1858           if (annotation.annotations[a].description != null)
1859           {
1860             ae.setDescription(annotation.annotations[a].description);
1861           }
1862           if (annotation.annotations[a].displayCharacter != null)
1863           {
1864             ae.setDisplayCharacter(
1865                     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(
1877                     annotation.annotations[a].secondaryStructure + "");
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(
1940               settings.getWsParamFile().replace("\n", "|\\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", new Object[]
2001             { 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(
2244               colours.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(
2256                 colours.getUserColourScheme().getColour(i + 24).getRGB(),
2257                 16));
2258       }
2259       ucs.setLowerCaseColours(newColours);
2260     }
2261
2262     return ucs;
2263   }
2264
2265   /**
2266    * contains last error message (if any) encountered by XML loader.
2267    */
2268   String errorMessage = null;
2269
2270   /**
2271    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2272    * exceptions are raised during project XML parsing
2273    */
2274   public boolean attemptversion1parse = true;
2275
2276   /**
2277    * Load a jalview project archive from a jar file
2278    * 
2279    * @param file
2280    *          - HTTP URL or filename
2281    */
2282   public AlignFrame loadJalviewAlign(final String file)
2283   {
2284
2285     jalview.gui.AlignFrame af = null;
2286
2287     try
2288     {
2289       // create list to store references for any new Jmol viewers created
2290       newStructureViewers = new Vector<>();
2291       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2292       // Workaround is to make sure caller implements the JarInputStreamProvider
2293       // interface
2294       // so we can re-open the jar input stream for each entry.
2295
2296       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2297       af = loadJalviewAlign(jprovider);
2298       af.setMenusForViewport();
2299
2300     } catch (MalformedURLException e)
2301     {
2302       errorMessage = "Invalid URL format for '" + file + "'";
2303       reportErrors();
2304     } finally
2305     {
2306       try
2307       {
2308         SwingUtilities.invokeAndWait(new Runnable()
2309         {
2310           @Override
2311           public void run()
2312           {
2313             setLoadingFinishedForNewStructureViewers();
2314           };
2315         });
2316       } catch (Exception x)
2317       {
2318         System.err.println("Error loading alignment: " + x.getMessage());
2319       }
2320     }
2321     return af;
2322   }
2323
2324   private jarInputStreamProvider createjarInputStreamProvider(
2325           final String file) throws MalformedURLException
2326   {
2327     URL url = null;
2328     errorMessage = null;
2329     uniqueSetSuffix = null;
2330     seqRefIds = null;
2331     viewportsAdded.clear();
2332     frefedSequence = null;
2333
2334     if (file.startsWith("http://"))
2335     {
2336       url = new URL(file);
2337     }
2338     final URL _url = url;
2339     return new jarInputStreamProvider()
2340     {
2341
2342       @Override
2343       public JarInputStream getJarInputStream() throws IOException
2344       {
2345         if (_url != null)
2346         {
2347           return new JarInputStream(_url.openStream());
2348         }
2349         else
2350         {
2351           return new JarInputStream(new FileInputStream(file));
2352         }
2353       }
2354
2355       @Override
2356       public String getFilename()
2357       {
2358         return file;
2359       }
2360     };
2361   }
2362
2363   /**
2364    * Recover jalview session from a jalview project archive. Caller may
2365    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2366    * themselves. Any null fields will be initialised with default values,
2367    * non-null fields are left alone.
2368    * 
2369    * @param jprovider
2370    * @return
2371    */
2372   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2373   {
2374     errorMessage = null;
2375     if (uniqueSetSuffix == null)
2376     {
2377       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2378     }
2379     if (seqRefIds == null)
2380     {
2381       initSeqRefs();
2382     }
2383     AlignFrame af = null, _af = null;
2384     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
2385     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
2386     final String file = jprovider.getFilename();
2387     try
2388     {
2389       JarInputStream jin = null;
2390       JarEntry jarentry = null;
2391       int entryCount = 1;
2392
2393       do
2394       {
2395         jin = jprovider.getJarInputStream();
2396         for (int i = 0; i < entryCount; i++)
2397         {
2398           jarentry = jin.getNextJarEntry();
2399         }
2400
2401         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2402         {
2403           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2404           JalviewModel object = new JalviewModel();
2405
2406           Unmarshaller unmar = new Unmarshaller(object);
2407           unmar.setValidation(false);
2408           object = (JalviewModel) unmar.unmarshal(in);
2409           if (true) // !skipViewport(object))
2410           {
2411             _af = loadFromObject(object, file, true, jprovider);
2412             if (_af != null && object.getJalviewModelSequence()
2413                     .getViewportCount() > 0)
2414             {
2415               if (af == null)
2416               {
2417                 // store a reference to the first view
2418                 af = _af;
2419               }
2420               if (_af.viewport.isGatherViewsHere())
2421               {
2422                 // if this is a gathered view, keep its reference since
2423                 // after gathering views, only this frame will remain
2424                 af = _af;
2425                 gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af);
2426               }
2427               // Save dataset to register mappings once all resolved
2428               importedDatasets.put(af.viewport.getAlignment().getDataset(),
2429                       af.viewport.getAlignment().getDataset());
2430             }
2431           }
2432           entryCount++;
2433         }
2434         else if (jarentry != null)
2435         {
2436           // Some other file here.
2437           entryCount++;
2438         }
2439       } while (jarentry != null);
2440       resolveFrefedSequences();
2441     } catch (IOException ex)
2442     {
2443       ex.printStackTrace();
2444       errorMessage = "Couldn't locate Jalview XML file : " + file;
2445       System.err.println(
2446               "Exception whilst loading jalview XML file : " + ex + "\n");
2447     } catch (Exception ex)
2448     {
2449       System.err.println("Parsing as Jalview Version 2 file failed.");
2450       ex.printStackTrace(System.err);
2451       if (attemptversion1parse)
2452       {
2453         // Is Version 1 Jar file?
2454         try
2455         {
2456           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2457         } catch (Exception ex2)
2458         {
2459           System.err.println("Exception whilst loading as jalviewXMLV1:");
2460           ex2.printStackTrace();
2461           af = null;
2462         }
2463       }
2464       if (Desktop.instance != null)
2465       {
2466         Desktop.instance.stopLoading();
2467       }
2468       if (af != null)
2469       {
2470         System.out.println("Successfully loaded archive file");
2471         return af;
2472       }
2473       ex.printStackTrace();
2474
2475       System.err.println(
2476               "Exception whilst loading jalview XML file : " + ex + "\n");
2477     } catch (OutOfMemoryError e)
2478     {
2479       // Don't use the OOM Window here
2480       errorMessage = "Out of memory loading jalview XML file";
2481       System.err.println("Out of memory whilst loading jalview XML file");
2482       e.printStackTrace();
2483     }
2484
2485     /*
2486      * Regather multiple views (with the same sequence set id) to the frame (if
2487      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2488      * views instead of separate frames. Note this doesn't restore a state where
2489      * some expanded views in turn have tabbed views - the last "first tab" read
2490      * in will play the role of gatherer for all.
2491      */
2492     for (AlignFrame fr : gatherToThisFrame.values())
2493     {
2494       Desktop.instance.gatherViews(fr);
2495     }
2496
2497     restoreSplitFrames();
2498     for (AlignmentI ds : importedDatasets.keySet())
2499     {
2500       if (ds.getCodonFrames() != null)
2501       {
2502         StructureSelectionManager
2503                 .getStructureSelectionManager(Desktop.instance)
2504                 .registerMappings(ds.getCodonFrames());
2505       }
2506     }
2507     if (errorMessage != null)
2508     {
2509       reportErrors();
2510     }
2511
2512     if (Desktop.instance != null)
2513     {
2514       Desktop.instance.stopLoading();
2515     }
2516
2517     return af;
2518   }
2519
2520   /**
2521    * Try to reconstruct and display SplitFrame windows, where each contains
2522    * complementary dna and protein alignments. Done by pairing up AlignFrame
2523    * objects (created earlier) which have complementary viewport ids associated.
2524    */
2525   protected void restoreSplitFrames()
2526   {
2527     List<SplitFrame> gatherTo = new ArrayList<>();
2528     List<AlignFrame> addedToSplitFrames = new ArrayList<>();
2529     Map<String, AlignFrame> dna = new HashMap<>();
2530
2531     /*
2532      * Identify the DNA alignments
2533      */
2534     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2535             .entrySet())
2536     {
2537       AlignFrame af = candidate.getValue();
2538       if (af.getViewport().getAlignment().isNucleotide())
2539       {
2540         dna.put(candidate.getKey().getId(), af);
2541       }
2542     }
2543
2544     /*
2545      * Try to match up the protein complements
2546      */
2547     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2548             .entrySet())
2549     {
2550       AlignFrame af = candidate.getValue();
2551       if (!af.getViewport().getAlignment().isNucleotide())
2552       {
2553         String complementId = candidate.getKey().getComplementId();
2554         // only non-null complements should be in the Map
2555         if (complementId != null && dna.containsKey(complementId))
2556         {
2557           final AlignFrame dnaFrame = dna.get(complementId);
2558           SplitFrame sf = createSplitFrame(dnaFrame, af);
2559           addedToSplitFrames.add(dnaFrame);
2560           addedToSplitFrames.add(af);
2561           dnaFrame.setMenusForViewport();
2562           af.setMenusForViewport();
2563           if (af.viewport.isGatherViewsHere())
2564           {
2565             gatherTo.add(sf);
2566           }
2567         }
2568       }
2569     }
2570
2571     /*
2572      * Open any that we failed to pair up (which shouldn't happen!) as
2573      * standalone AlignFrame's.
2574      */
2575     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2576             .entrySet())
2577     {
2578       AlignFrame af = candidate.getValue();
2579       if (!addedToSplitFrames.contains(af))
2580       {
2581         Viewport view = candidate.getKey();
2582         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2583                 view.getHeight());
2584         af.setMenusForViewport();
2585         System.err.println("Failed to restore view " + view.getTitle()
2586                 + " to split frame");
2587       }
2588     }
2589
2590     /*
2591      * Gather back into tabbed views as flagged.
2592      */
2593     for (SplitFrame sf : gatherTo)
2594     {
2595       Desktop.instance.gatherViews(sf);
2596     }
2597
2598     splitFrameCandidates.clear();
2599   }
2600
2601   /**
2602    * Construct and display one SplitFrame holding DNA and protein alignments.
2603    * 
2604    * @param dnaFrame
2605    * @param proteinFrame
2606    * @return
2607    */
2608   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2609           AlignFrame proteinFrame)
2610   {
2611     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2612     String title = MessageManager.getString("label.linked_view_title");
2613     int width = (int) dnaFrame.getBounds().getWidth();
2614     int height = (int) (dnaFrame.getBounds().getHeight()
2615             + proteinFrame.getBounds().getHeight() + 50);
2616
2617     /*
2618      * SplitFrame location is saved to both enclosed frames
2619      */
2620     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
2621     Desktop.addInternalFrame(splitFrame, title, width, height);
2622
2623     /*
2624      * And compute cDNA consensus (couldn't do earlier with consensus as
2625      * mappings were not yet present)
2626      */
2627     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2628
2629     return splitFrame;
2630   }
2631
2632   /**
2633    * check errorMessage for a valid error message and raise an error box in the
2634    * GUI or write the current errorMessage to stderr and then clear the error
2635    * state.
2636    */
2637   protected void reportErrors()
2638   {
2639     reportErrors(false);
2640   }
2641
2642   protected void reportErrors(final boolean saving)
2643   {
2644     if (errorMessage != null)
2645     {
2646       final String finalErrorMessage = errorMessage;
2647       if (raiseGUI)
2648       {
2649         javax.swing.SwingUtilities.invokeLater(new Runnable()
2650         {
2651           @Override
2652           public void run()
2653           {
2654             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2655                     finalErrorMessage,
2656                     "Error " + (saving ? "saving" : "loading")
2657                             + " Jalview file",
2658                     JvOptionPane.WARNING_MESSAGE);
2659           }
2660         });
2661       }
2662       else
2663       {
2664         System.err.println("Problem loading Jalview file: " + errorMessage);
2665       }
2666     }
2667     errorMessage = null;
2668   }
2669
2670   Map<String, String> alreadyLoadedPDB = new HashMap<>();
2671
2672   /**
2673    * when set, local views will be updated from view stored in JalviewXML
2674    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2675    * sync if this is set to true.
2676    */
2677   private final boolean updateLocalViews = false;
2678
2679   /**
2680    * Returns the path to a temporary file holding the PDB file for the given PDB
2681    * id. The first time of asking, searches for a file of that name in the
2682    * Jalview project jar, and copies it to a new temporary file. Any repeat
2683    * requests just return the path to the file previously created.
2684    * 
2685    * @param jprovider
2686    * @param pdbId
2687    * @return
2688    */
2689   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
2690           String origFile)
2691   {
2692     if (alreadyLoadedPDB.containsKey(pdbId))
2693     {
2694       return alreadyLoadedPDB.get(pdbId).toString();
2695     }
2696
2697     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
2698             origFile);
2699     if (tempFile != null)
2700     {
2701       alreadyLoadedPDB.put(pdbId, tempFile);
2702     }
2703     return tempFile;
2704   }
2705
2706   /**
2707    * Copies the jar entry of given name to a new temporary file and returns the
2708    * path to the file, or null if the entry is not found.
2709    * 
2710    * @param jprovider
2711    * @param jarEntryName
2712    * @param prefix
2713    *          a prefix for the temporary file name, must be at least three
2714    *          characters long
2715    * @param origFile
2716    *          null or original file - so new file can be given the same suffix
2717    *          as the old one
2718    * @return
2719    */
2720   protected String copyJarEntry(jarInputStreamProvider jprovider,
2721           String jarEntryName, String prefix, String origFile)
2722   {
2723     BufferedReader in = null;
2724     PrintWriter out = null;
2725     String suffix = ".tmp";
2726     if (origFile == null)
2727     {
2728       origFile = jarEntryName;
2729     }
2730     int sfpos = origFile.lastIndexOf(".");
2731     if (sfpos > -1 && sfpos < (origFile.length() - 3))
2732     {
2733       suffix = "." + origFile.substring(sfpos + 1);
2734     }
2735     try
2736     {
2737       JarInputStream jin = jprovider.getJarInputStream();
2738       /*
2739        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2740        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2741        * FileInputStream(jprovider)); }
2742        */
2743
2744       JarEntry entry = null;
2745       do
2746       {
2747         entry = jin.getNextJarEntry();
2748       } while (entry != null && !entry.getName().equals(jarEntryName));
2749       if (entry != null)
2750       {
2751         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2752         File outFile = File.createTempFile(prefix, suffix);
2753         outFile.deleteOnExit();
2754         out = new PrintWriter(new FileOutputStream(outFile));
2755         String data;
2756
2757         while ((data = in.readLine()) != null)
2758         {
2759           out.println(data);
2760         }
2761         out.flush();
2762         String t = outFile.getAbsolutePath();
2763         return t;
2764       }
2765       else
2766       {
2767         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2768       }
2769     } catch (Exception ex)
2770     {
2771       ex.printStackTrace();
2772     } finally
2773     {
2774       if (in != null)
2775       {
2776         try
2777         {
2778           in.close();
2779         } catch (IOException e)
2780         {
2781           // ignore
2782         }
2783       }
2784       if (out != null)
2785       {
2786         out.close();
2787       }
2788     }
2789
2790     return null;
2791   }
2792
2793   private class JvAnnotRow
2794   {
2795     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2796     {
2797       order = i;
2798       template = jaa;
2799     }
2800
2801     /**
2802      * persisted version of annotation row from which to take vis properties
2803      */
2804     public jalview.datamodel.AlignmentAnnotation template;
2805
2806     /**
2807      * original position of the annotation row in the alignment
2808      */
2809     public int order;
2810   }
2811
2812   /**
2813    * Load alignment frame from jalview XML DOM object
2814    * 
2815    * @param object
2816    *          DOM
2817    * @param file
2818    *          filename source string
2819    * @param loadTreesAndStructures
2820    *          when false only create Viewport
2821    * @param jprovider
2822    *          data source provider
2823    * @return alignment frame created from view stored in DOM
2824    */
2825   AlignFrame loadFromObject(JalviewModel object, String file,
2826           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2827   {
2828     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2829     Sequence[] vamsasSeq = vamsasSet.getSequence();
2830
2831     JalviewModelSequence jms = object.getJalviewModelSequence();
2832
2833     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2834             : null;
2835
2836     // ////////////////////////////////
2837     // LOAD SEQUENCES
2838
2839     List<SequenceI> hiddenSeqs = null;
2840
2841     List<SequenceI> tmpseqs = new ArrayList<>();
2842
2843     boolean multipleView = false;
2844     SequenceI referenceseqForView = null;
2845     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2846     int vi = 0; // counter in vamsasSeq array
2847     for (int i = 0; i < jseqs.length; i++)
2848     {
2849       String seqId = jseqs[i].getId();
2850
2851       SequenceI tmpSeq = seqRefIds.get(seqId);
2852       if (tmpSeq != null)
2853       {
2854         if (!incompleteSeqs.containsKey(seqId))
2855         {
2856           // may not need this check, but keep it for at least 2.9,1 release
2857           if (tmpSeq.getStart() != jseqs[i].getStart()
2858                   || tmpSeq.getEnd() != jseqs[i].getEnd())
2859           {
2860             System.err.println(
2861                     "Warning JAL-2154 regression: updating start/end for sequence "
2862                             + tmpSeq.toString() + " to " + jseqs[i]);
2863           }
2864         }
2865         else
2866         {
2867           incompleteSeqs.remove(seqId);
2868         }
2869         if (vamsasSeq.length > vi && vamsasSeq[vi].getId().equals(seqId))
2870         {
2871           // most likely we are reading a dataset XML document so
2872           // update from vamsasSeq section of XML for this sequence
2873           tmpSeq.setName(vamsasSeq[vi].getName());
2874           tmpSeq.setDescription(vamsasSeq[vi].getDescription());
2875           tmpSeq.setSequence(vamsasSeq[vi].getSequence());
2876           vi++;
2877         }
2878         else
2879         {
2880           // reading multiple views, so vamsasSeq set is a subset of JSeq
2881           multipleView = true;
2882         }
2883         tmpSeq.setStart(jseqs[i].getStart());
2884         tmpSeq.setEnd(jseqs[i].getEnd());
2885         tmpseqs.add(tmpSeq);
2886       }
2887       else
2888       {
2889         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2890                 vamsasSeq[vi].getSequence());
2891         tmpSeq.setDescription(vamsasSeq[vi].getDescription());
2892         tmpSeq.setStart(jseqs[i].getStart());
2893         tmpSeq.setEnd(jseqs[i].getEnd());
2894         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
2895         seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq);
2896         tmpseqs.add(tmpSeq);
2897         vi++;
2898       }
2899
2900       if (jseqs[i].hasViewreference() && jseqs[i].getViewreference())
2901       {
2902         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
2903       }
2904
2905       if (jseqs[i].getHidden())
2906       {
2907         if (hiddenSeqs == null)
2908         {
2909           hiddenSeqs = new ArrayList<>();
2910         }
2911
2912         hiddenSeqs.add(tmpSeq);
2913       }
2914     }
2915
2916     // /
2917     // Create the alignment object from the sequence set
2918     // ///////////////////////////////
2919     SequenceI[] orderedSeqs = tmpseqs
2920             .toArray(new SequenceI[tmpseqs.size()]);
2921
2922     AlignmentI al = null;
2923     // so we must create or recover the dataset alignment before going further
2924     // ///////////////////////////////
2925     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2926     {
2927       // older jalview projects do not have a dataset - so creat alignment and
2928       // dataset
2929       al = new Alignment(orderedSeqs);
2930       al.setDataset(null);
2931     }
2932     else
2933     {
2934       boolean isdsal = object.getJalviewModelSequence()
2935               .getViewportCount() == 0;
2936       if (isdsal)
2937       {
2938         // we are importing a dataset record, so
2939         // recover reference to an alignment already materialsed as dataset
2940         al = getDatasetFor(vamsasSet.getDatasetId());
2941       }
2942       if (al == null)
2943       {
2944         // materialse the alignment
2945         al = new Alignment(orderedSeqs);
2946       }
2947       if (isdsal)
2948       {
2949         addDatasetRef(vamsasSet.getDatasetId(), al);
2950       }
2951
2952       // finally, verify all data in vamsasSet is actually present in al
2953       // passing on flag indicating if it is actually a stored dataset
2954       recoverDatasetFor(vamsasSet, al, isdsal);
2955     }
2956
2957     if (referenceseqForView != null)
2958     {
2959       al.setSeqrep(referenceseqForView);
2960     }
2961     // / Add the alignment properties
2962     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2963     {
2964       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2965       al.setProperty(ssp.getKey(), ssp.getValue());
2966     }
2967
2968     // ///////////////////////////////
2969
2970     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2971     if (!multipleView)
2972     {
2973       // load sequence features, database references and any associated PDB
2974       // structures for the alignment
2975       //
2976       // prior to 2.10, this part would only be executed the first time a
2977       // sequence was encountered, but not afterwards.
2978       // now, for 2.10 projects, this is also done if the xml doc includes
2979       // dataset sequences not actually present in any particular view.
2980       //
2981       for (int i = 0; i < vamsasSeq.length; i++)
2982       {
2983         if (jseqs[i].getFeaturesCount() > 0)
2984         {
2985           Features[] features = jseqs[i].getFeatures();
2986           for (int f = 0; f < features.length; f++)
2987           {
2988             SequenceFeature sf = new SequenceFeature(features[f].getType(),
2989                     features[f].getDescription(), features[f].getBegin(),
2990                     features[f].getEnd(), features[f].getScore(),
2991                     features[f].getFeatureGroup());
2992             sf.setStatus(features[f].getStatus());
2993             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2994             {
2995               OtherData keyValue = features[f].getOtherData(od);
2996               if (keyValue.getKey().startsWith("LINK"))
2997               {
2998                 sf.addLink(keyValue.getValue());
2999               }
3000               else
3001               {
3002                 sf.setValue(keyValue.getKey(), keyValue.getValue());
3003               }
3004
3005             }
3006             // adds feature to datasequence's feature set (since Jalview 2.10)
3007             al.getSequenceAt(i).addSequenceFeature(sf);
3008           }
3009         }
3010         if (vamsasSeq[i].getDBRefCount() > 0)
3011         {
3012           // adds dbrefs to datasequence's set (since Jalview 2.10)
3013           addDBRefs(
3014                   al.getSequenceAt(i).getDatasetSequence() == null
3015                           ? al.getSequenceAt(i)
3016                           : al.getSequenceAt(i).getDatasetSequence(),
3017                   vamsasSeq[i]);
3018         }
3019         if (jseqs[i].getPdbidsCount() > 0)
3020         {
3021           Pdbids[] ids = jseqs[i].getPdbids();
3022           for (int p = 0; p < ids.length; p++)
3023           {
3024             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
3025             entry.setId(ids[p].getId());
3026             if (ids[p].getType() != null)
3027             {
3028               if (PDBEntry.Type.getType(ids[p].getType()) != null)
3029               {
3030                 entry.setType(PDBEntry.Type.getType(ids[p].getType()));
3031               }
3032               else
3033               {
3034                 entry.setType(PDBEntry.Type.FILE);
3035               }
3036             }
3037             // jprovider is null when executing 'New View'
3038             if (ids[p].getFile() != null && jprovider != null)
3039             {
3040               if (!pdbloaded.containsKey(ids[p].getFile()))
3041               {
3042                 entry.setFile(loadPDBFile(jprovider, ids[p].getId(),
3043                         ids[p].getFile()));
3044               }
3045               else
3046               {
3047                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
3048               }
3049             }
3050             if (ids[p].getPdbentryItem() != null)
3051             {
3052               for (PdbentryItem item : ids[p].getPdbentryItem())
3053               {
3054                 for (Property pr : item.getProperty())
3055                 {
3056                   entry.setProperty(pr.getName(), pr.getValue());
3057                 }
3058               }
3059             }
3060             StructureSelectionManager
3061                     .getStructureSelectionManager(Desktop.instance)
3062                     .registerPDBEntry(entry);
3063             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3064             if (al.getSequenceAt(i).getDatasetSequence() != null)
3065             {
3066               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3067             }
3068             else
3069             {
3070               al.getSequenceAt(i).addPDBId(entry);
3071             }
3072           }
3073         }
3074       }
3075     } // end !multipleview
3076
3077     // ///////////////////////////////
3078     // LOAD SEQUENCE MAPPINGS
3079
3080     if (vamsasSet.getAlcodonFrameCount() > 0)
3081     {
3082       // TODO Potentially this should only be done once for all views of an
3083       // alignment
3084       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
3085       for (int i = 0; i < alc.length; i++)
3086       {
3087         AlignedCodonFrame cf = new AlignedCodonFrame();
3088         if (alc[i].getAlcodMapCount() > 0)
3089         {
3090           AlcodMap[] maps = alc[i].getAlcodMap();
3091           for (int m = 0; m < maps.length; m++)
3092           {
3093             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
3094             // Load Mapping
3095             jalview.datamodel.Mapping mapping = null;
3096             // attach to dna sequence reference.
3097             if (maps[m].getMapping() != null)
3098             {
3099               mapping = addMapping(maps[m].getMapping());
3100               if (dnaseq != null && mapping.getTo() != null)
3101               {
3102                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3103               }
3104               else
3105               {
3106                 // defer to later
3107                 frefedSequence.add(
3108                         newAlcodMapRef(maps[m].getDnasq(), cf, mapping));
3109               }
3110             }
3111           }
3112           al.addCodonFrame(cf);
3113         }
3114       }
3115     }
3116
3117     // ////////////////////////////////
3118     // LOAD ANNOTATIONS
3119     List<JvAnnotRow> autoAlan = new ArrayList<>();
3120
3121     /*
3122      * store any annotations which forward reference a group's ID
3123      */
3124     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
3125
3126     if (vamsasSet.getAnnotationCount() > 0)
3127     {
3128       Annotation[] an = vamsasSet.getAnnotation();
3129
3130       for (int i = 0; i < an.length; i++)
3131       {
3132         Annotation annotation = an[i];
3133
3134         /**
3135          * test if annotation is automatically calculated for this view only
3136          */
3137         boolean autoForView = false;
3138         if (annotation.getLabel().equals("Quality")
3139                 || annotation.getLabel().equals("Conservation")
3140                 || annotation.getLabel().equals("Consensus"))
3141         {
3142           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3143           autoForView = true;
3144           if (!annotation.hasAutoCalculated())
3145           {
3146             annotation.setAutoCalculated(true);
3147           }
3148         }
3149         if (autoForView || (annotation.hasAutoCalculated()
3150                 && annotation.isAutoCalculated()))
3151         {
3152           // remove ID - we don't recover annotation from other views for
3153           // view-specific annotation
3154           annotation.setId(null);
3155         }
3156
3157         // set visiblity for other annotation in this view
3158         String annotationId = annotation.getId();
3159         if (annotationId != null && annotationIds.containsKey(annotationId))
3160         {
3161           AlignmentAnnotation jda = annotationIds.get(annotationId);
3162           // in principle Visible should always be true for annotation displayed
3163           // in multiple views
3164           if (annotation.hasVisible())
3165           {
3166             jda.visible = annotation.getVisible();
3167           }
3168
3169           al.addAnnotation(jda);
3170
3171           continue;
3172         }
3173         // Construct new annotation from model.
3174         AnnotationElement[] ae = annotation.getAnnotationElement();
3175         jalview.datamodel.Annotation[] anot = null;
3176         java.awt.Color firstColour = null;
3177         int anpos;
3178         if (!annotation.getScoreOnly())
3179         {
3180           anot = new jalview.datamodel.Annotation[al.getWidth()];
3181           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
3182           {
3183             anpos = ae[aa].getPosition();
3184
3185             if (anpos >= anot.length)
3186             {
3187               continue;
3188             }
3189
3190             anot[anpos] = new jalview.datamodel.Annotation(
3191
3192                     ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
3193                     (ae[aa].getSecondaryStructure() == null
3194                             || ae[aa].getSecondaryStructure().length() == 0)
3195                                     ? ' '
3196                                     : ae[aa].getSecondaryStructure()
3197                                             .charAt(0),
3198                     ae[aa].getValue()
3199
3200             );
3201             // JBPNote: Consider verifying dataflow for IO of secondary
3202             // structure annotation read from Stockholm files
3203             // this was added to try to ensure that
3204             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
3205             // {
3206             // anot[ae[aa].getPosition()].displayCharacter = "";
3207             // }
3208             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
3209             if (firstColour == null)
3210             {
3211               firstColour = anot[anpos].colour;
3212             }
3213           }
3214         }
3215         jalview.datamodel.AlignmentAnnotation jaa = null;
3216
3217         if (annotation.getGraph())
3218         {
3219           float llim = 0, hlim = 0;
3220           // if (autoForView || an[i].isAutoCalculated()) {
3221           // hlim=11f;
3222           // }
3223           jaa = new jalview.datamodel.AlignmentAnnotation(
3224                   annotation.getLabel(), annotation.getDescription(), anot,
3225                   llim, hlim, annotation.getGraphType());
3226
3227           jaa.graphGroup = annotation.getGraphGroup();
3228           jaa._linecolour = firstColour;
3229           if (annotation.getThresholdLine() != null)
3230           {
3231             jaa.setThreshold(new jalview.datamodel.GraphLine(
3232                     annotation.getThresholdLine().getValue(),
3233                     annotation.getThresholdLine().getLabel(),
3234                     new java.awt.Color(
3235                             annotation.getThresholdLine().getColour())));
3236
3237           }
3238           if (autoForView || annotation.isAutoCalculated())
3239           {
3240             // Hardwire the symbol display line to ensure that labels for
3241             // histograms are displayed
3242             jaa.hasText = true;
3243           }
3244         }
3245         else
3246         {
3247           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
3248                   an[i].getDescription(), anot);
3249           jaa._linecolour = firstColour;
3250         }
3251         // register new annotation
3252         if (an[i].getId() != null)
3253         {
3254           annotationIds.put(an[i].getId(), jaa);
3255           jaa.annotationId = an[i].getId();
3256         }
3257         // recover sequence association
3258         String sequenceRef = an[i].getSequenceRef();
3259         if (sequenceRef != null)
3260         {
3261           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3262           SequenceI sequence = seqRefIds.get(sequenceRef);
3263           if (sequence == null)
3264           {
3265             // in pre-2.9 projects sequence ref is to sequence name
3266             sequence = al.findName(sequenceRef);
3267           }
3268           if (sequence != null)
3269           {
3270             jaa.createSequenceMapping(sequence, 1, true);
3271             sequence.addAlignmentAnnotation(jaa);
3272           }
3273         }
3274         // and make a note of any group association
3275         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3276         {
3277           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3278                   .get(an[i].getGroupRef());
3279           if (aal == null)
3280           {
3281             aal = new ArrayList<>();
3282             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3283           }
3284           aal.add(jaa);
3285         }
3286
3287         if (an[i].hasScore())
3288         {
3289           jaa.setScore(an[i].getScore());
3290         }
3291         if (an[i].hasVisible())
3292         {
3293           jaa.visible = an[i].getVisible();
3294         }
3295
3296         if (an[i].hasCentreColLabels())
3297         {
3298           jaa.centreColLabels = an[i].getCentreColLabels();
3299         }
3300
3301         if (an[i].hasScaleColLabels())
3302         {
3303           jaa.scaleColLabel = an[i].getScaleColLabels();
3304         }
3305         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3306         {
3307           // newer files have an 'autoCalculated' flag and store calculation
3308           // state in viewport properties
3309           jaa.autoCalculated = true; // means annotation will be marked for
3310           // update at end of load.
3311         }
3312         if (an[i].hasGraphHeight())
3313         {
3314           jaa.graphHeight = an[i].getGraphHeight();
3315         }
3316         if (an[i].hasBelowAlignment())
3317         {
3318           jaa.belowAlignment = an[i].isBelowAlignment();
3319         }
3320         jaa.setCalcId(an[i].getCalcId());
3321         if (an[i].getPropertyCount() > 0)
3322         {
3323           for (jalview.schemabinding.version2.Property prop : an[i]
3324                   .getProperty())
3325           {
3326             jaa.setProperty(prop.getName(), prop.getValue());
3327           }
3328         }
3329         if (jaa.autoCalculated)
3330         {
3331           autoAlan.add(new JvAnnotRow(i, jaa));
3332         }
3333         else
3334         // if (!autoForView)
3335         {
3336           // add autocalculated group annotation and any user created annotation
3337           // for the view
3338           al.addAnnotation(jaa);
3339         }
3340       }
3341     }
3342     // ///////////////////////
3343     // LOAD GROUPS
3344     // Create alignment markup and styles for this view
3345     if (jms.getJGroupCount() > 0)
3346     {
3347       JGroup[] groups = jms.getJGroup();
3348       boolean addAnnotSchemeGroup = false;
3349       for (int i = 0; i < groups.length; i++)
3350       {
3351         JGroup jGroup = groups[i];
3352         ColourSchemeI cs = null;
3353         if (jGroup.getColour() != null)
3354         {
3355           if (jGroup.getColour().startsWith("ucs"))
3356           {
3357             cs = getUserColourScheme(jms, jGroup.getColour());
3358           }
3359           else if (jGroup.getColour().equals("AnnotationColourGradient")
3360                   && jGroup.getAnnotationColours() != null)
3361           {
3362             addAnnotSchemeGroup = true;
3363           }
3364           else
3365           {
3366             cs = ColourSchemeProperty.getColourScheme(al,
3367                     jGroup.getColour());
3368           }
3369         }
3370         int pidThreshold = jGroup.getPidThreshold();
3371
3372         Vector<SequenceI> seqs = new Vector<>();
3373
3374         for (int s = 0; s < jGroup.getSeqCount(); s++)
3375         {
3376           String seqId = jGroup.getSeq(s) + "";
3377           SequenceI ts = seqRefIds.get(seqId);
3378
3379           if (ts != null)
3380           {
3381             seqs.addElement(ts);
3382           }
3383         }
3384
3385         if (seqs.size() < 1)
3386         {
3387           continue;
3388         }
3389
3390         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3391                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3392                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3393         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
3394         sg.getGroupColourScheme()
3395                 .setConservationInc(jGroup.getConsThreshold());
3396         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3397
3398         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3399         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3400         sg.setShowNonconserved(
3401                 jGroup.hasShowUnconserved() ? jGroup.isShowUnconserved()
3402                         : false);
3403         sg.thresholdTextColour = jGroup.getTextColThreshold();
3404         if (jGroup.hasShowConsensusHistogram())
3405         {
3406           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3407         }
3408         ;
3409         if (jGroup.hasShowSequenceLogo())
3410         {
3411           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3412         }
3413         if (jGroup.hasNormaliseSequenceLogo())
3414         {
3415           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3416         }
3417         if (jGroup.hasIgnoreGapsinConsensus())
3418         {
3419           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3420         }
3421         if (jGroup.getConsThreshold() != 0)
3422         {
3423           Conservation c = new Conservation("All", sg.getSequences(null), 0,
3424                   sg.getWidth() - 1);
3425           c.calculate();
3426           c.verdict(false, 25);
3427           sg.cs.setConservation(c);
3428         }
3429
3430         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3431         {
3432           // re-instate unique group/annotation row reference
3433           List<AlignmentAnnotation> jaal = groupAnnotRefs
3434                   .get(jGroup.getId());
3435           if (jaal != null)
3436           {
3437             for (AlignmentAnnotation jaa : jaal)
3438             {
3439               jaa.groupRef = sg;
3440               if (jaa.autoCalculated)
3441               {
3442                 // match up and try to set group autocalc alignment row for this
3443                 // annotation
3444                 if (jaa.label.startsWith("Consensus for "))
3445                 {
3446                   sg.setConsensus(jaa);
3447                 }
3448                 // match up and try to set group autocalc alignment row for this
3449                 // annotation
3450                 if (jaa.label.startsWith("Conservation for "))
3451                 {
3452                   sg.setConservationRow(jaa);
3453                 }
3454               }
3455             }
3456           }
3457         }
3458         al.addGroup(sg);
3459         if (addAnnotSchemeGroup)
3460         {
3461           // reconstruct the annotation colourscheme
3462           sg.setColourScheme(constructAnnotationColour(
3463                   jGroup.getAnnotationColours(), null, al, jms, false));
3464         }
3465       }
3466     }
3467     if (view == null)
3468     {
3469       // only dataset in this model, so just return.
3470       return null;
3471     }
3472     // ///////////////////////////////
3473     // LOAD VIEWPORT
3474
3475     // If we just load in the same jar file again, the sequenceSetId
3476     // will be the same, and we end up with multiple references
3477     // to the same sequenceSet. We must modify this id on load
3478     // so that each load of the file gives a unique id
3479     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3480     String viewId = (view.getId() == null ? null
3481             : view.getId() + uniqueSetSuffix);
3482     AlignFrame af = null;
3483     AlignViewport av = null;
3484     // now check to see if we really need to create a new viewport.
3485     if (multipleView && viewportsAdded.size() == 0)
3486     {
3487       // We recovered an alignment for which a viewport already exists.
3488       // TODO: fix up any settings necessary for overlaying stored state onto
3489       // state recovered from another document. (may not be necessary).
3490       // we may need a binding from a viewport in memory to one recovered from
3491       // XML.
3492       // and then recover its containing af to allow the settings to be applied.
3493       // TODO: fix for vamsas demo
3494       System.err.println(
3495               "About to recover a viewport for existing alignment: Sequence set ID is "
3496                       + uniqueSeqSetId);
3497       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3498       if (seqsetobj != null)
3499       {
3500         if (seqsetobj instanceof String)
3501         {
3502           uniqueSeqSetId = (String) seqsetobj;
3503           System.err.println(
3504                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3505                           + uniqueSeqSetId);
3506         }
3507         else
3508         {
3509           System.err.println(
3510                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
3511         }
3512
3513       }
3514     }
3515     /**
3516      * indicate that annotation colours are applied across all groups (pre
3517      * Jalview 2.8.1 behaviour)
3518      */
3519     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
3520             object.getVersion());
3521
3522     AlignmentPanel ap = null;
3523     boolean isnewview = true;
3524     if (viewId != null)
3525     {
3526       // Check to see if this alignment already has a view id == viewId
3527       jalview.gui.AlignmentPanel views[] = Desktop
3528               .getAlignmentPanels(uniqueSeqSetId);
3529       if (views != null && views.length > 0)
3530       {
3531         for (int v = 0; v < views.length; v++)
3532         {
3533           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3534           {
3535             // recover the existing alignpanel, alignframe, viewport
3536             af = views[v].alignFrame;
3537             av = views[v].av;
3538             ap = views[v];
3539             // TODO: could even skip resetting view settings if we don't want to
3540             // change the local settings from other jalview processes
3541             isnewview = false;
3542           }
3543         }
3544       }
3545     }
3546
3547     if (isnewview)
3548     {
3549       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3550               uniqueSeqSetId, viewId, autoAlan);
3551       av = af.viewport;
3552       ap = af.alignPanel;
3553     }
3554
3555     /*
3556      * Load any trees, PDB structures and viewers
3557      * 
3558      * Not done if flag is false (when this method is used for New View)
3559      */
3560     if (loadTreesAndStructures)
3561     {
3562       loadTrees(jms, view, af, av, ap);
3563       loadPDBStructures(jprovider, jseqs, af, ap);
3564       loadRnaViewers(jprovider, jseqs, ap);
3565     }
3566     // and finally return.
3567     return af;
3568   }
3569
3570   /**
3571    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3572    * panel is restored from separate jar entries, two (gapped and trimmed) per
3573    * sequence and secondary structure.
3574    * 
3575    * Currently each viewer shows just one sequence and structure (gapped and
3576    * trimmed), however this method is designed to support multiple sequences or
3577    * structures in viewers if wanted in future.
3578    * 
3579    * @param jprovider
3580    * @param jseqs
3581    * @param ap
3582    */
3583   private void loadRnaViewers(jarInputStreamProvider jprovider,
3584           JSeq[] jseqs, AlignmentPanel ap)
3585   {
3586     /*
3587      * scan the sequences for references to viewers; create each one the first
3588      * time it is referenced, add Rna models to existing viewers
3589      */
3590     for (JSeq jseq : jseqs)
3591     {
3592       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3593       {
3594         RnaViewer viewer = jseq.getRnaViewer(i);
3595         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
3596                 ap);
3597
3598         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3599         {
3600           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3601           SequenceI seq = seqRefIds.get(jseq.getId());
3602           AlignmentAnnotation ann = this.annotationIds
3603                   .get(ss.getAnnotationId());
3604
3605           /*
3606            * add the structure to the Varna display (with session state copied
3607            * from the jar to a temporary file)
3608            */
3609           boolean gapped = ss.isGapped();
3610           String rnaTitle = ss.getTitle();
3611           String sessionState = ss.getViewerState();
3612           String tempStateFile = copyJarEntry(jprovider, sessionState,
3613                   "varna", null);
3614           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3615           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3616         }
3617         appVarna.setInitialSelection(viewer.getSelectedRna());
3618       }
3619     }
3620   }
3621
3622   /**
3623    * Locate and return an already instantiated matching AppVarna, or create one
3624    * if not found
3625    * 
3626    * @param viewer
3627    * @param viewIdSuffix
3628    * @param ap
3629    * @return
3630    */
3631   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3632           String viewIdSuffix, AlignmentPanel ap)
3633   {
3634     /*
3635      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3636      * if load is repeated
3637      */
3638     String postLoadId = viewer.getViewId() + viewIdSuffix;
3639     for (JInternalFrame frame : getAllFrames())
3640     {
3641       if (frame instanceof AppVarna)
3642       {
3643         AppVarna varna = (AppVarna) frame;
3644         if (postLoadId.equals(varna.getViewId()))
3645         {
3646           // this viewer is already instantiated
3647           // could in future here add ap as another 'parent' of the
3648           // AppVarna window; currently just 1-to-many
3649           return varna;
3650         }
3651       }
3652     }
3653
3654     /*
3655      * viewer not found - make it
3656      */
3657     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
3658             viewer.getXpos(), viewer.getYpos(), viewer.getWidth(),
3659             viewer.getHeight(), viewer.getDividerLocation());
3660     AppVarna varna = new AppVarna(model, ap);
3661
3662     return varna;
3663   }
3664
3665   /**
3666    * Load any saved trees
3667    * 
3668    * @param jms
3669    * @param view
3670    * @param af
3671    * @param av
3672    * @param ap
3673    */
3674   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3675           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3676   {
3677     // TODO result of automated refactoring - are all these parameters needed?
3678     try
3679     {
3680       for (int t = 0; t < jms.getTreeCount(); t++)
3681       {
3682
3683         Tree tree = jms.getTree(t);
3684
3685         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3686         if (tp == null)
3687         {
3688           tp = af.showNewickTree(
3689                   new jalview.io.NewickFile(tree.getNewick()),
3690                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3691                   tree.getXpos(), tree.getYpos());
3692           if (tree.getId() != null)
3693           {
3694             // perhaps bind the tree id to something ?
3695           }
3696         }
3697         else
3698         {
3699           // update local tree attributes ?
3700           // TODO: should check if tp has been manipulated by user - if so its
3701           // settings shouldn't be modified
3702           tp.setTitle(tree.getTitle());
3703           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(),
3704                   tree.getWidth(), tree.getHeight()));
3705           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3706           // views'
3707           // works still
3708           tp.treeCanvas.av = av; // af.viewport;
3709           tp.treeCanvas.ap = ap; // af.alignPanel;
3710
3711         }
3712         if (tp == null)
3713         {
3714           warn("There was a problem recovering stored Newick tree: \n"
3715                   + tree.getNewick());
3716           continue;
3717         }
3718
3719         tp.fitToWindow.setState(tree.getFitToWindow());
3720         tp.fitToWindow_actionPerformed(null);
3721
3722         if (tree.getFontName() != null)
3723         {
3724           tp.setTreeFont(new java.awt.Font(tree.getFontName(),
3725                   tree.getFontStyle(), tree.getFontSize()));
3726         }
3727         else
3728         {
3729           tp.setTreeFont(new java.awt.Font(view.getFontName(),
3730                   view.getFontStyle(), tree.getFontSize()));
3731         }
3732
3733         tp.showPlaceholders(tree.getMarkUnlinked());
3734         tp.showBootstrap(tree.getShowBootstrap());
3735         tp.showDistances(tree.getShowDistances());
3736
3737         tp.treeCanvas.threshold = tree.getThreshold();
3738
3739         if (tree.getCurrentTree())
3740         {
3741           af.viewport.setCurrentTree(tp.getTree());
3742         }
3743       }
3744
3745     } catch (Exception ex)
3746     {
3747       ex.printStackTrace();
3748     }
3749   }
3750
3751   /**
3752    * Load and link any saved structure viewers.
3753    * 
3754    * @param jprovider
3755    * @param jseqs
3756    * @param af
3757    * @param ap
3758    */
3759   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3760           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3761   {
3762     /*
3763      * Run through all PDB ids on the alignment, and collect mappings between
3764      * distinct view ids and all sequences referring to that view.
3765      */
3766     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
3767
3768     for (int i = 0; i < jseqs.length; i++)
3769     {
3770       if (jseqs[i].getPdbidsCount() > 0)
3771       {
3772         Pdbids[] ids = jseqs[i].getPdbids();
3773         for (int p = 0; p < ids.length; p++)
3774         {
3775           final int structureStateCount = ids[p].getStructureStateCount();
3776           for (int s = 0; s < structureStateCount; s++)
3777           {
3778             // check to see if we haven't already created this structure view
3779             final StructureState structureState = ids[p]
3780                     .getStructureState(s);
3781             String sviewid = (structureState.getViewId() == null) ? null
3782                     : structureState.getViewId() + uniqueSetSuffix;
3783             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3784             // Originally : ids[p].getFile()
3785             // : TODO: verify external PDB file recovery still works in normal
3786             // jalview project load
3787             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId(),
3788                     ids[p].getFile()));
3789             jpdb.setId(ids[p].getId());
3790
3791             int x = structureState.getXpos();
3792             int y = structureState.getYpos();
3793             int width = structureState.getWidth();
3794             int height = structureState.getHeight();
3795
3796             // Probably don't need to do this anymore...
3797             // Desktop.desktop.getComponentAt(x, y);
3798             // TODO: NOW: check that this recovers the PDB file correctly.
3799             String pdbFile = loadPDBFile(jprovider, ids[p].getId(),
3800                     ids[p].getFile());
3801             jalview.datamodel.SequenceI seq = seqRefIds
3802                     .get(jseqs[i].getId() + "");
3803             if (sviewid == null)
3804             {
3805               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
3806                       + height;
3807             }
3808             if (!structureViewers.containsKey(sviewid))
3809             {
3810               structureViewers.put(sviewid,
3811                       new StructureViewerModel(x, y, width, height, false,
3812                               false, true, structureState.getViewId(),
3813                               structureState.getType()));
3814               // Legacy pre-2.7 conversion JAL-823 :
3815               // do not assume any view has to be linked for colour by
3816               // sequence
3817             }
3818
3819             // assemble String[] { pdb files }, String[] { id for each
3820             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3821             // seqs_file 2}, boolean[] {
3822             // linkAlignPanel,superposeWithAlignpanel}} from hash
3823             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3824             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3825                     | (structureState.hasAlignwithAlignPanel()
3826                             ? structureState.getAlignwithAlignPanel()
3827                             : false));
3828
3829             /*
3830              * Default colour by linked panel to false if not specified (e.g.
3831              * for pre-2.7 projects)
3832              */
3833             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3834             colourWithAlignPanel |= (structureState
3835                     .hasColourwithAlignPanel()
3836                             ? structureState.getColourwithAlignPanel()
3837                             : false);
3838             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3839
3840             /*
3841              * Default colour by viewer to true if not specified (e.g. for
3842              * pre-2.7 projects)
3843              */
3844             boolean colourByViewer = jmoldat.isColourByViewer();
3845             colourByViewer &= structureState.hasColourByJmol()
3846                     ? structureState.getColourByJmol()
3847                     : true;
3848             jmoldat.setColourByViewer(colourByViewer);
3849
3850             if (jmoldat.getStateData().length() < structureState
3851                     .getContent().length())
3852             {
3853               {
3854                 jmoldat.setStateData(structureState.getContent());
3855               }
3856             }
3857             if (ids[p].getFile() != null)
3858             {
3859               File mapkey = new File(ids[p].getFile());
3860               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3861               if (seqstrmaps == null)
3862               {
3863                 jmoldat.getFileData().put(mapkey,
3864                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3865                                 ids[p].getId()));
3866               }
3867               if (!seqstrmaps.getSeqList().contains(seq))
3868               {
3869                 seqstrmaps.getSeqList().add(seq);
3870                 // TODO and chains?
3871               }
3872             }
3873             else
3874             {
3875               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");
3876               warn(errorMessage);
3877             }
3878           }
3879         }
3880       }
3881     }
3882     // Instantiate the associated structure views
3883     for (Entry<String, StructureViewerModel> entry : structureViewers
3884             .entrySet())
3885     {
3886       try
3887       {
3888         createOrLinkStructureViewer(entry, af, ap, jprovider);
3889       } catch (Exception e)
3890       {
3891         System.err.println(
3892                 "Error loading structure viewer: " + e.getMessage());
3893         // failed - try the next one
3894       }
3895     }
3896   }
3897
3898   /**
3899    * 
3900    * @param viewerData
3901    * @param af
3902    * @param ap
3903    * @param jprovider
3904    */
3905   protected void createOrLinkStructureViewer(
3906           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3907           AlignmentPanel ap, jarInputStreamProvider jprovider)
3908   {
3909     final StructureViewerModel stateData = viewerData.getValue();
3910
3911     /*
3912      * Search for any viewer windows already open from other alignment views
3913      * that exactly match the stored structure state
3914      */
3915     StructureViewerBase comp = findMatchingViewer(viewerData);
3916
3917     if (comp != null)
3918     {
3919       linkStructureViewer(ap, comp, stateData);
3920       return;
3921     }
3922
3923     /*
3924      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3925      * "viewer_"+stateData.viewId
3926      */
3927     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3928     {
3929       createChimeraViewer(viewerData, af, jprovider);
3930     }
3931     else
3932     {
3933       /*
3934        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3935        */
3936       createJmolViewer(viewerData, af, jprovider);
3937     }
3938   }
3939
3940   /**
3941    * Create a new Chimera viewer.
3942    * 
3943    * @param data
3944    * @param af
3945    * @param jprovider
3946    */
3947   protected void createChimeraViewer(
3948           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3949           jarInputStreamProvider jprovider)
3950   {
3951     StructureViewerModel data = viewerData.getValue();
3952     String chimeraSessionFile = data.getStateData();
3953
3954     /*
3955      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3956      * 
3957      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3958      * 'uniquified' sviewid used to reconstruct the viewer here
3959      */
3960     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3961     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3962             "chimera", null);
3963
3964     Set<Entry<File, StructureData>> fileData = data.getFileData()
3965             .entrySet();
3966     List<PDBEntry> pdbs = new ArrayList<>();
3967     List<SequenceI[]> allseqs = new ArrayList<>();
3968     for (Entry<File, StructureData> pdb : fileData)
3969     {
3970       String filePath = pdb.getValue().getFilePath();
3971       String pdbId = pdb.getValue().getPdbId();
3972       // pdbs.add(new PDBEntry(filePath, pdbId));
3973       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3974       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3975       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3976       allseqs.add(seqs);
3977     }
3978
3979     boolean colourByChimera = data.isColourByViewer();
3980     boolean colourBySequence = data.isColourWithAlignPanel();
3981
3982     // TODO use StructureViewer as a factory here, see JAL-1761
3983     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3984     final SequenceI[][] seqsArray = allseqs
3985             .toArray(new SequenceI[allseqs.size()][]);
3986     String newViewId = viewerData.getKey();
3987
3988     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3989             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3990             colourBySequence, newViewId);
3991     cvf.setSize(data.getWidth(), data.getHeight());
3992     cvf.setLocation(data.getX(), data.getY());
3993   }
3994
3995   /**
3996    * Create a new Jmol window. First parse the Jmol state to translate filenames
3997    * loaded into the view, and record the order in which files are shown in the
3998    * Jmol view, so we can add the sequence mappings in same order.
3999    * 
4000    * @param viewerData
4001    * @param af
4002    * @param jprovider
4003    */
4004   protected void createJmolViewer(
4005           final Entry<String, StructureViewerModel> viewerData,
4006           AlignFrame af, jarInputStreamProvider jprovider)
4007   {
4008     final StructureViewerModel svattrib = viewerData.getValue();
4009     String state = svattrib.getStateData();
4010
4011     /*
4012      * Pre-2.9: state element value is the Jmol state string
4013      * 
4014      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4015      * + viewId
4016      */
4017     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4018     {
4019       state = readJarEntry(jprovider,
4020               getViewerJarEntryName(svattrib.getViewId()));
4021     }
4022
4023     List<String> pdbfilenames = new ArrayList<>();
4024     List<SequenceI[]> seqmaps = new ArrayList<>();
4025     List<String> pdbids = new ArrayList<>();
4026     StringBuilder newFileLoc = new StringBuilder(64);
4027     int cp = 0, ncp, ecp;
4028     Map<File, StructureData> oldFiles = svattrib.getFileData();
4029     while ((ncp = state.indexOf("load ", cp)) > -1)
4030     {
4031       do
4032       {
4033         // look for next filename in load statement
4034         newFileLoc.append(state.substring(cp,
4035                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4036         String oldfilenam = state.substring(ncp,
4037                 ecp = state.indexOf("\"", ncp));
4038         // recover the new mapping data for this old filename
4039         // have to normalize filename - since Jmol and jalview do
4040         // filename
4041         // translation differently.
4042         StructureData filedat = oldFiles.get(new File(oldfilenam));
4043         if (filedat == null)
4044         {
4045           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4046           filedat = oldFiles.get(new File(reformatedOldFilename));
4047         }
4048         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4049         pdbfilenames.add(filedat.getFilePath());
4050         pdbids.add(filedat.getPdbId());
4051         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4052         newFileLoc.append("\"");
4053         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4054                       // look for next file statement.
4055       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4056     }
4057     if (cp > 0)
4058     {
4059       // just append rest of state
4060       newFileLoc.append(state.substring(cp));
4061     }
4062     else
4063     {
4064       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4065       newFileLoc = new StringBuilder(state);
4066       newFileLoc.append("; load append ");
4067       for (File id : oldFiles.keySet())
4068       {
4069         // add this and any other pdb files that should be present in
4070         // the viewer
4071         StructureData filedat = oldFiles.get(id);
4072         newFileLoc.append(filedat.getFilePath());
4073         pdbfilenames.add(filedat.getFilePath());
4074         pdbids.add(filedat.getPdbId());
4075         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4076         newFileLoc.append(" \"");
4077         newFileLoc.append(filedat.getFilePath());
4078         newFileLoc.append("\"");
4079
4080       }
4081       newFileLoc.append(";");
4082     }
4083
4084     if (newFileLoc.length() == 0)
4085     {
4086       return;
4087     }
4088     int histbug = newFileLoc.indexOf("history = ");
4089     if (histbug > -1)
4090     {
4091       /*
4092        * change "history = [true|false];" to "history = [1|0];"
4093        */
4094       histbug += 10;
4095       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4096       String val = (diff == -1) ? null
4097               : newFileLoc.substring(histbug, diff);
4098       if (val != null && val.length() >= 4)
4099       {
4100         if (val.contains("e")) // eh? what can it be?
4101         {
4102           if (val.trim().equals("true"))
4103           {
4104             val = "1";
4105           }
4106           else
4107           {
4108             val = "0";
4109           }
4110           newFileLoc.replace(histbug, diff, val);
4111         }
4112       }
4113     }
4114
4115     final String[] pdbf = pdbfilenames
4116             .toArray(new String[pdbfilenames.size()]);
4117     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4118     final SequenceI[][] sq = seqmaps
4119             .toArray(new SequenceI[seqmaps.size()][]);
4120     final String fileloc = newFileLoc.toString();
4121     final String sviewid = viewerData.getKey();
4122     final AlignFrame alf = af;
4123     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4124             svattrib.getWidth(), svattrib.getHeight());
4125     try
4126     {
4127       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4128       {
4129         @Override
4130         public void run()
4131         {
4132           JalviewStructureDisplayI sview = null;
4133           try
4134           {
4135             sview = new StructureViewer(
4136                     alf.alignPanel.getStructureSelectionManager())
4137                             .createView(StructureViewer.ViewerType.JMOL,
4138                                     pdbf, id, sq, alf.alignPanel, svattrib,
4139                                     fileloc, rect, sviewid);
4140             addNewStructureViewer(sview);
4141           } catch (OutOfMemoryError ex)
4142           {
4143             new OOMWarning("restoring structure view for PDB id " + id,
4144                     (OutOfMemoryError) ex.getCause());
4145             if (sview != null && sview.isVisible())
4146             {
4147               sview.closeViewer(false);
4148               sview.setVisible(false);
4149               sview.dispose();
4150             }
4151           }
4152         }
4153       });
4154     } catch (InvocationTargetException ex)
4155     {
4156       warn("Unexpected error when opening Jmol view.", ex);
4157
4158     } catch (InterruptedException e)
4159     {
4160       // e.printStackTrace();
4161     }
4162
4163   }
4164
4165   /**
4166    * Generates a name for the entry in the project jar file to hold state
4167    * information for a structure viewer
4168    * 
4169    * @param viewId
4170    * @return
4171    */
4172   protected String getViewerJarEntryName(String viewId)
4173   {
4174     return VIEWER_PREFIX + viewId;
4175   }
4176
4177   /**
4178    * Returns any open frame that matches given structure viewer data. The match
4179    * is based on the unique viewId, or (for older project versions) the frame's
4180    * geometry.
4181    * 
4182    * @param viewerData
4183    * @return
4184    */
4185   protected StructureViewerBase findMatchingViewer(
4186           Entry<String, StructureViewerModel> viewerData)
4187   {
4188     final String sviewid = viewerData.getKey();
4189     final StructureViewerModel svattrib = viewerData.getValue();
4190     StructureViewerBase comp = null;
4191     JInternalFrame[] frames = getAllFrames();
4192     for (JInternalFrame frame : frames)
4193     {
4194       if (frame instanceof StructureViewerBase)
4195       {
4196         /*
4197          * Post jalview 2.4 schema includes structure view id
4198          */
4199         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4200                 .equals(sviewid))
4201         {
4202           comp = (StructureViewerBase) frame;
4203           break; // break added in 2.9
4204         }
4205         /*
4206          * Otherwise test for matching position and size of viewer frame
4207          */
4208         else if (frame.getX() == svattrib.getX()
4209                 && frame.getY() == svattrib.getY()
4210                 && frame.getHeight() == svattrib.getHeight()
4211                 && frame.getWidth() == svattrib.getWidth())
4212         {
4213           comp = (StructureViewerBase) frame;
4214           // no break in faint hope of an exact match on viewId
4215         }
4216       }
4217     }
4218     return comp;
4219   }
4220
4221   /**
4222    * Link an AlignmentPanel to an existing structure viewer.
4223    * 
4224    * @param ap
4225    * @param viewer
4226    * @param oldFiles
4227    * @param useinViewerSuperpos
4228    * @param usetoColourbyseq
4229    * @param viewerColouring
4230    */
4231   protected void linkStructureViewer(AlignmentPanel ap,
4232           StructureViewerBase viewer, StructureViewerModel stateData)
4233   {
4234     // NOTE: if the jalview project is part of a shared session then
4235     // view synchronization should/could be done here.
4236
4237     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4238     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4239     final boolean viewerColouring = stateData.isColourByViewer();
4240     Map<File, StructureData> oldFiles = stateData.getFileData();
4241
4242     /*
4243      * Add mapping for sequences in this view to an already open viewer
4244      */
4245     final AAStructureBindingModel binding = viewer.getBinding();
4246     for (File id : oldFiles.keySet())
4247     {
4248       // add this and any other pdb files that should be present in the
4249       // viewer
4250       StructureData filedat = oldFiles.get(id);
4251       String pdbFile = filedat.getFilePath();
4252       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4253       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE);
4254       binding.addSequenceForStructFile(pdbFile, seq);
4255     }
4256     // and add the AlignmentPanel's reference to the view panel
4257     viewer.addAlignmentPanel(ap);
4258     if (useinViewerSuperpos)
4259     {
4260       viewer.useAlignmentPanelForSuperposition(ap);
4261     }
4262     else
4263     {
4264       viewer.excludeAlignmentPanelForSuperposition(ap);
4265     }
4266     if (usetoColourbyseq)
4267     {
4268       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4269     }
4270     else
4271     {
4272       viewer.excludeAlignmentPanelForColourbyseq(ap);
4273     }
4274   }
4275
4276   /**
4277    * Get all frames within the Desktop.
4278    * 
4279    * @return
4280    */
4281   protected JInternalFrame[] getAllFrames()
4282   {
4283     JInternalFrame[] frames = null;
4284     // TODO is this necessary - is it safe - risk of hanging?
4285     do
4286     {
4287       try
4288       {
4289         frames = Desktop.desktop.getAllFrames();
4290       } catch (ArrayIndexOutOfBoundsException e)
4291       {
4292         // occasional No such child exceptions are thrown here...
4293         try
4294         {
4295           Thread.sleep(10);
4296         } catch (InterruptedException f)
4297         {
4298         }
4299       }
4300     } while (frames == null);
4301     return frames;
4302   }
4303
4304   /**
4305    * Answers true if 'version' is equal to or later than 'supported', where each
4306    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4307    * changes. Development and test values for 'version' are leniently treated
4308    * i.e. answer true.
4309    * 
4310    * @param supported
4311    *          - minimum version we are comparing against
4312    * @param version
4313    *          - version of data being processsed
4314    * @return
4315    */
4316   public static boolean isVersionStringLaterThan(String supported,
4317           String version)
4318   {
4319     if (supported == null || version == null
4320             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4321             || version.equalsIgnoreCase("Test")
4322             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4323     {
4324       System.err.println("Assuming project file with "
4325               + (version == null ? "null" : version)
4326               + " is compatible with Jalview version " + supported);
4327       return true;
4328     }
4329     else
4330     {
4331       return StringUtils.compareVersions(version, supported, "b") >= 0;
4332     }
4333   }
4334
4335   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4336
4337   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4338   {
4339     if (newStructureViewers != null)
4340     {
4341       sview.getBinding().setFinishedLoadingFromArchive(false);
4342       newStructureViewers.add(sview);
4343     }
4344   }
4345
4346   protected void setLoadingFinishedForNewStructureViewers()
4347   {
4348     if (newStructureViewers != null)
4349     {
4350       for (JalviewStructureDisplayI sview : newStructureViewers)
4351       {
4352         sview.getBinding().setFinishedLoadingFromArchive(true);
4353       }
4354       newStructureViewers.clear();
4355       newStructureViewers = null;
4356     }
4357   }
4358
4359   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4360           List<SequenceI> hiddenSeqs, AlignmentI al,
4361           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4362           String viewId, List<JvAnnotRow> autoAlan)
4363   {
4364     AlignFrame af = null;
4365     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4366             uniqueSeqSetId, viewId);
4367
4368     af.setFileName(file, FileFormat.Jalview);
4369
4370     for (int i = 0; i < JSEQ.length; i++)
4371     {
4372       af.viewport.setSequenceColour(
4373               af.viewport.getAlignment().getSequenceAt(i),
4374               new java.awt.Color(JSEQ[i].getColour()));
4375     }
4376
4377     if (al.hasSeqrep())
4378     {
4379       af.getViewport().setColourByReferenceSeq(true);
4380       af.getViewport().setDisplayReferenceSeq(true);
4381     }
4382
4383     af.viewport.setGatherViewsHere(view.getGatheredViews());
4384
4385     if (view.getSequenceSetId() != null)
4386     {
4387       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4388
4389       af.viewport.setSequenceSetId(uniqueSeqSetId);
4390       if (av != null)
4391       {
4392         // propagate shared settings to this new view
4393         af.viewport.setHistoryList(av.getHistoryList());
4394         af.viewport.setRedoList(av.getRedoList());
4395       }
4396       else
4397       {
4398         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4399       }
4400       // TODO: check if this method can be called repeatedly without
4401       // side-effects if alignpanel already registered.
4402       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4403     }
4404     // apply Hidden regions to view.
4405     if (hiddenSeqs != null)
4406     {
4407       for (int s = 0; s < JSEQ.length; s++)
4408       {
4409         SequenceGroup hidden = new SequenceGroup();
4410         boolean isRepresentative = false;
4411         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4412         {
4413           isRepresentative = true;
4414           SequenceI sequenceToHide = al
4415                   .getSequenceAt(JSEQ[s].getHiddenSequences(r));
4416           hidden.addSequence(sequenceToHide, false);
4417           // remove from hiddenSeqs list so we don't try to hide it twice
4418           hiddenSeqs.remove(sequenceToHide);
4419         }
4420         if (isRepresentative)
4421         {
4422           SequenceI representativeSequence = al.getSequenceAt(s);
4423           hidden.addSequence(representativeSequence, false);
4424           af.viewport.hideRepSequences(representativeSequence, hidden);
4425         }
4426       }
4427
4428       SequenceI[] hseqs = hiddenSeqs
4429               .toArray(new SequenceI[hiddenSeqs.size()]);
4430       af.viewport.hideSequence(hseqs);
4431
4432     }
4433     // recover view properties and display parameters
4434
4435     af.viewport.setShowAnnotation(view.getShowAnnotation());
4436     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4437     af.viewport.setThreshold(view.getPidThreshold());
4438
4439     af.viewport.setColourText(view.getShowColourText());
4440
4441     af.viewport.setConservationSelected(view.getConservationSelected());
4442     af.viewport.setIncrement(view.getConsThreshold());
4443     af.viewport.setShowJVSuffix(view.getShowFullId());
4444     af.viewport.setRightAlignIds(view.getRightAlignIds());
4445     af.viewport.setFont(new java.awt.Font(view.getFontName(),
4446             view.getFontStyle(), view.getFontSize()), true);
4447     ViewStyleI vs = af.viewport.getViewStyle();
4448     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4449     af.viewport.setViewStyle(vs);
4450     // TODO: allow custom charWidth/Heights to be restored by updating them
4451     // after setting font - which means set above to false
4452     af.viewport.setRenderGaps(view.getRenderGaps());
4453     af.viewport.setWrapAlignment(view.getWrapAlignment());
4454     af.viewport.setShowAnnotation(view.getShowAnnotation());
4455
4456     af.viewport.setShowBoxes(view.getShowBoxes());
4457
4458     af.viewport.setShowText(view.getShowText());
4459
4460     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4461     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4462     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4463     af.viewport.setShowUnconserved(
4464             view.hasShowUnconserved() ? view.isShowUnconserved() : false);
4465     af.viewport.getRanges().setStartRes(view.getStartRes());
4466
4467     if (view.getViewName() != null)
4468     {
4469       af.viewport.viewName = view.getViewName();
4470       af.setInitialTabVisible();
4471     }
4472     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4473             view.getHeight());
4474     // startSeq set in af.alignPanel.updateLayout below
4475     af.alignPanel.updateLayout();
4476     ColourSchemeI cs = null;
4477     // apply colourschemes
4478     if (view.getBgColour() != null)
4479     {
4480       if (view.getBgColour().startsWith("ucs"))
4481       {
4482         cs = getUserColourScheme(jms, view.getBgColour());
4483       }
4484       else if (view.getBgColour().startsWith("Annotation"))
4485       {
4486         AnnotationColours viewAnnColour = view.getAnnotationColours();
4487         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4488
4489         // annpos
4490
4491       }
4492       else
4493       {
4494         cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour());
4495       }
4496     }
4497
4498     af.viewport.setGlobalColourScheme(cs);
4499     af.viewport.getResidueShading().setThreshold(view.getPidThreshold(),
4500             view.getIgnoreGapsinConsensus());
4501     af.viewport.getResidueShading()
4502             .setConsensus(af.viewport.getSequenceConsensusHash());
4503     af.viewport.setColourAppliesToAllGroups(false);
4504
4505     if (view.getConservationSelected() && cs != null)
4506     {
4507       af.viewport.getResidueShading()
4508               .setConservationInc(view.getConsThreshold());
4509     }
4510
4511     af.changeColour(cs);
4512
4513     af.viewport.setColourAppliesToAllGroups(true);
4514
4515     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4516
4517     if (view.hasCentreColumnLabels())
4518     {
4519       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4520     }
4521     if (view.hasIgnoreGapsinConsensus())
4522     {
4523       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4524               null);
4525     }
4526     if (view.hasFollowHighlight())
4527     {
4528       af.viewport.setFollowHighlight(view.getFollowHighlight());
4529     }
4530     if (view.hasFollowSelection())
4531     {
4532       af.viewport.followSelection = view.getFollowSelection();
4533     }
4534     if (view.hasShowConsensusHistogram())
4535     {
4536       af.viewport
4537               .setShowConsensusHistogram(view.getShowConsensusHistogram());
4538     }
4539     else
4540     {
4541       af.viewport.setShowConsensusHistogram(true);
4542     }
4543     if (view.hasShowSequenceLogo())
4544     {
4545       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4546     }
4547     else
4548     {
4549       af.viewport.setShowSequenceLogo(false);
4550     }
4551     if (view.hasNormaliseSequenceLogo())
4552     {
4553       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4554     }
4555     if (view.hasShowDbRefTooltip())
4556     {
4557       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4558     }
4559     if (view.hasShowNPfeatureTooltip())
4560     {
4561       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4562     }
4563     if (view.hasShowGroupConsensus())
4564     {
4565       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4566     }
4567     else
4568     {
4569       af.viewport.setShowGroupConsensus(false);
4570     }
4571     if (view.hasShowGroupConservation())
4572     {
4573       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4574     }
4575     else
4576     {
4577       af.viewport.setShowGroupConservation(false);
4578     }
4579
4580     // recover featre settings
4581     if (jms.getFeatureSettings() != null)
4582     {
4583       FeaturesDisplayed fdi;
4584       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4585       String[] renderOrder = new String[jms.getFeatureSettings()
4586               .getSettingCount()];
4587       Map<String, FeatureColourI> featureColours = new Hashtable<>();
4588       Map<String, Float> featureOrder = new Hashtable<>();
4589
4590       for (int fs = 0; fs < jms.getFeatureSettings()
4591               .getSettingCount(); fs++)
4592       {
4593         Setting setting = jms.getFeatureSettings().getSetting(fs);
4594         if (setting.hasMincolour())
4595         {
4596           FeatureColourI gc = setting.hasMin()
4597                   ? new FeatureColour(new Color(setting.getMincolour()),
4598                           new Color(setting.getColour()), setting.getMin(),
4599                           setting.getMax())
4600                   : new FeatureColour(new Color(setting.getMincolour()),
4601                           new Color(setting.getColour()), 0, 1);
4602           if (setting.hasThreshold())
4603           {
4604             gc.setThreshold(setting.getThreshold());
4605             int threshstate = setting.getThreshstate();
4606             // -1 = None, 0 = Below, 1 = Above threshold
4607             if (threshstate == 0)
4608             {
4609               gc.setBelowThreshold(true);
4610             }
4611             else if (threshstate == 1)
4612             {
4613               gc.setAboveThreshold(true);
4614             }
4615           }
4616           gc.setAutoScaled(true); // default
4617           if (setting.hasAutoScale())
4618           {
4619             gc.setAutoScaled(setting.getAutoScale());
4620           }
4621           if (setting.hasColourByLabel())
4622           {
4623             gc.setColourByLabel(setting.getColourByLabel());
4624           }
4625           // and put in the feature colour table.
4626           featureColours.put(setting.getType(), gc);
4627         }
4628         else
4629         {
4630           featureColours.put(setting.getType(),
4631                   new FeatureColour(new Color(setting.getColour())));
4632         }
4633         renderOrder[fs] = setting.getType();
4634         if (setting.hasOrder())
4635         {
4636           featureOrder.put(setting.getType(), setting.getOrder());
4637         }
4638         else
4639         {
4640           featureOrder.put(setting.getType(), new Float(
4641                   fs / jms.getFeatureSettings().getSettingCount()));
4642         }
4643         if (setting.getDisplay())
4644         {
4645           fdi.setVisible(setting.getType());
4646         }
4647       }
4648       Map<String, Boolean> fgtable = new Hashtable<>();
4649       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4650       {
4651         Group grp = jms.getFeatureSettings().getGroup(gs);
4652         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4653       }
4654       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4655       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4656       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4657       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4658               fgtable, featureColours, 1.0f, featureOrder);
4659       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4660               .transferSettings(frs);
4661
4662     }
4663
4664     if (view.getHiddenColumnsCount() > 0)
4665     {
4666       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4667       {
4668         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(),
4669                 view.getHiddenColumns(c).getEnd() // +1
4670         );
4671       }
4672     }
4673     if (view.getCalcIdParam() != null)
4674     {
4675       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4676       {
4677         if (calcIdParam != null)
4678         {
4679           if (recoverCalcIdParam(calcIdParam, af.viewport))
4680           {
4681           }
4682           else
4683           {
4684             warn("Couldn't recover parameters for "
4685                     + calcIdParam.getCalcId());
4686           }
4687         }
4688       }
4689     }
4690     af.setMenusFromViewport(af.viewport);
4691     af.setTitle(view.getTitle());
4692     // TODO: we don't need to do this if the viewport is aready visible.
4693     /*
4694      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4695      * has a 'cdna/protein complement' view, in which case save it in order to
4696      * populate a SplitFrame once all views have been read in.
4697      */
4698     String complementaryViewId = view.getComplementId();
4699     if (complementaryViewId == null)
4700     {
4701       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4702               view.getHeight());
4703       // recompute any autoannotation
4704       af.alignPanel.updateAnnotation(false, true);
4705       reorderAutoannotation(af, al, autoAlan);
4706       af.alignPanel.alignmentChanged();
4707     }
4708     else
4709     {
4710       splitFrameCandidates.put(view, af);
4711     }
4712     return af;
4713   }
4714
4715   /**
4716    * Reads saved data to restore Colour by Annotation settings
4717    * 
4718    * @param viewAnnColour
4719    * @param af
4720    * @param al
4721    * @param jms
4722    * @param checkGroupAnnColour
4723    * @return
4724    */
4725   private ColourSchemeI constructAnnotationColour(
4726           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
4727           JalviewModelSequence jms, boolean checkGroupAnnColour)
4728   {
4729     boolean propagateAnnColour = false;
4730     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4731     if (checkGroupAnnColour && al.getGroups() != null
4732             && al.getGroups().size() > 0)
4733     {
4734       // pre 2.8.1 behaviour
4735       // check to see if we should transfer annotation colours
4736       propagateAnnColour = true;
4737       for (SequenceGroup sg : al.getGroups())
4738       {
4739         if (sg.getColourScheme() instanceof AnnotationColourGradient)
4740         {
4741           propagateAnnColour = false;
4742         }
4743       }
4744     }
4745
4746     /*
4747      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
4748      */
4749     String annotationId = viewAnnColour.getAnnotation();
4750     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
4751
4752     /*
4753      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
4754      */
4755     if (matchedAnnotation == null
4756             && annAlignment.getAlignmentAnnotation() != null)
4757     {
4758       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4759       {
4760         if (annotationId
4761                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
4762         {
4763           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
4764           break;
4765         }
4766       }
4767     }
4768     if (matchedAnnotation == null)
4769     {
4770       System.err.println("Failed to match annotation colour scheme for "
4771               + annotationId);
4772       return null;
4773     }
4774     if (matchedAnnotation.getThreshold() == null)
4775     {
4776       matchedAnnotation.setThreshold(new GraphLine(
4777               viewAnnColour.getThreshold(), "Threshold", Color.black));
4778     }
4779
4780     AnnotationColourGradient cs = null;
4781     if (viewAnnColour.getColourScheme().equals("None"))
4782     {
4783       cs = new AnnotationColourGradient(matchedAnnotation,
4784               new Color(viewAnnColour.getMinColour()),
4785               new Color(viewAnnColour.getMaxColour()),
4786               viewAnnColour.getAboveThreshold());
4787     }
4788     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4789     {
4790       cs = new AnnotationColourGradient(matchedAnnotation,
4791               getUserColourScheme(jms, viewAnnColour.getColourScheme()),
4792               viewAnnColour.getAboveThreshold());
4793     }
4794     else
4795     {
4796       cs = new AnnotationColourGradient(matchedAnnotation,
4797               ColourSchemeProperty.getColourScheme(al,
4798                       viewAnnColour.getColourScheme()),
4799               viewAnnColour.getAboveThreshold());
4800     }
4801
4802     boolean perSequenceOnly = viewAnnColour.isPerSequence();
4803     boolean useOriginalColours = viewAnnColour.isPredefinedColours();
4804     cs.setSeqAssociated(perSequenceOnly);
4805     cs.setPredefinedColours(useOriginalColours);
4806
4807     if (propagateAnnColour && al.getGroups() != null)
4808     {
4809       // Also use these settings for all the groups
4810       for (int g = 0; g < al.getGroups().size(); g++)
4811       {
4812         SequenceGroup sg = al.getGroups().get(g);
4813         if (sg.getGroupColourScheme() == null)
4814         {
4815           continue;
4816         }
4817
4818         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
4819                 matchedAnnotation, sg.getColourScheme(),
4820                 viewAnnColour.getAboveThreshold());
4821         sg.setColourScheme(groupScheme);
4822         groupScheme.setSeqAssociated(perSequenceOnly);
4823         groupScheme.setPredefinedColours(useOriginalColours);
4824       }
4825     }
4826     return cs;
4827   }
4828
4829   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
4830           List<JvAnnotRow> autoAlan)
4831   {
4832     // copy over visualization settings for autocalculated annotation in the
4833     // view
4834     if (al.getAlignmentAnnotation() != null)
4835     {
4836       /**
4837        * Kludge for magic autoannotation names (see JAL-811)
4838        */
4839       String[] magicNames = new String[] { "Consensus", "Quality",
4840           "Conservation" };
4841       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4842       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
4843       for (String nm : magicNames)
4844       {
4845         visan.put(nm, nullAnnot);
4846       }
4847       for (JvAnnotRow auan : autoAlan)
4848       {
4849         visan.put(auan.template.label
4850                 + (auan.template.getCalcId() == null ? ""
4851                         : "\t" + auan.template.getCalcId()),
4852                 auan);
4853       }
4854       int hSize = al.getAlignmentAnnotation().length;
4855       List<JvAnnotRow> reorder = new ArrayList<>();
4856       // work through any autoCalculated annotation already on the view
4857       // removing it if it should be placed in a different location on the
4858       // annotation panel.
4859       List<String> remains = new ArrayList<>(visan.keySet());
4860       for (int h = 0; h < hSize; h++)
4861       {
4862         jalview.datamodel.AlignmentAnnotation jalan = al
4863                 .getAlignmentAnnotation()[h];
4864         if (jalan.autoCalculated)
4865         {
4866           String k;
4867           JvAnnotRow valan = visan.get(k = jalan.label);
4868           if (jalan.getCalcId() != null)
4869           {
4870             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4871           }
4872
4873           if (valan != null)
4874           {
4875             // delete the auto calculated row from the alignment
4876             al.deleteAnnotation(jalan, false);
4877             remains.remove(k);
4878             hSize--;
4879             h--;
4880             if (valan != nullAnnot)
4881             {
4882               if (jalan != valan.template)
4883               {
4884                 // newly created autoannotation row instance
4885                 // so keep a reference to the visible annotation row
4886                 // and copy over all relevant attributes
4887                 if (valan.template.graphHeight >= 0)
4888
4889                 {
4890                   jalan.graphHeight = valan.template.graphHeight;
4891                 }
4892                 jalan.visible = valan.template.visible;
4893               }
4894               reorder.add(new JvAnnotRow(valan.order, jalan));
4895             }
4896           }
4897         }
4898       }
4899       // Add any (possibly stale) autocalculated rows that were not appended to
4900       // the view during construction
4901       for (String other : remains)
4902       {
4903         JvAnnotRow othera = visan.get(other);
4904         if (othera != nullAnnot && othera.template.getCalcId() != null
4905                 && othera.template.getCalcId().length() > 0)
4906         {
4907           reorder.add(othera);
4908         }
4909       }
4910       // now put the automatic annotation in its correct place
4911       int s = 0, srt[] = new int[reorder.size()];
4912       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4913       for (JvAnnotRow jvar : reorder)
4914       {
4915         rws[s] = jvar;
4916         srt[s++] = jvar.order;
4917       }
4918       reorder.clear();
4919       jalview.util.QuickSort.sort(srt, rws);
4920       // and re-insert the annotation at its correct position
4921       for (JvAnnotRow jvar : rws)
4922       {
4923         al.addAnnotation(jvar.template, jvar.order);
4924       }
4925       af.alignPanel.adjustAnnotationHeight();
4926     }
4927   }
4928
4929   Hashtable skipList = null;
4930
4931   /**
4932    * TODO remove this method
4933    * 
4934    * @param view
4935    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4936    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4937    *         throw new Error("Implementation Error. No skipList defined for this
4938    *         Jalview2XML instance."); } return (AlignFrame)
4939    *         skipList.get(view.getSequenceSetId()); }
4940    */
4941
4942   /**
4943    * Check if the Jalview view contained in object should be skipped or not.
4944    * 
4945    * @param object
4946    * @return true if view's sequenceSetId is a key in skipList
4947    */
4948   private boolean skipViewport(JalviewModel object)
4949   {
4950     if (skipList == null)
4951     {
4952       return false;
4953     }
4954     String id;
4955     if (skipList.containsKey(
4956             id = object.getJalviewModelSequence().getViewport()[0]
4957                     .getSequenceSetId()))
4958     {
4959       if (Cache.log != null && Cache.log.isDebugEnabled())
4960       {
4961         Cache.log.debug("Skipping seuqence set id " + id);
4962       }
4963       return true;
4964     }
4965     return false;
4966   }
4967
4968   public void addToSkipList(AlignFrame af)
4969   {
4970     if (skipList == null)
4971     {
4972       skipList = new Hashtable();
4973     }
4974     skipList.put(af.getViewport().getSequenceSetId(), af);
4975   }
4976
4977   public void clearSkipList()
4978   {
4979     if (skipList != null)
4980     {
4981       skipList.clear();
4982       skipList = null;
4983     }
4984   }
4985
4986   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
4987           boolean ignoreUnrefed)
4988   {
4989     jalview.datamodel.AlignmentI ds = getDatasetFor(
4990             vamsasSet.getDatasetId());
4991     Vector dseqs = null;
4992     if (ds == null)
4993     {
4994       // create a list of new dataset sequences
4995       dseqs = new Vector();
4996     }
4997     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4998     {
4999       Sequence vamsasSeq = vamsasSet.getSequence(i);
5000       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5001     }
5002     // create a new dataset
5003     if (ds == null)
5004     {
5005       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5006       dseqs.copyInto(dsseqs);
5007       ds = new jalview.datamodel.Alignment(dsseqs);
5008       debug("Created new dataset " + vamsasSet.getDatasetId()
5009               + " for alignment " + System.identityHashCode(al));
5010       addDatasetRef(vamsasSet.getDatasetId(), ds);
5011     }
5012     // set the dataset for the newly imported alignment.
5013     if (al.getDataset() == null && !ignoreUnrefed)
5014     {
5015       al.setDataset(ds);
5016     }
5017   }
5018
5019   /**
5020    * 
5021    * @param vamsasSeq
5022    *          sequence definition to create/merge dataset sequence for
5023    * @param ds
5024    *          dataset alignment
5025    * @param dseqs
5026    *          vector to add new dataset sequence to
5027    * @param ignoreUnrefed
5028    *          - when true, don't create new sequences from vamsasSeq if it's id
5029    *          doesn't already have an asssociated Jalview sequence.
5030    * @param vseqpos
5031    *          - used to reorder the sequence in the alignment according to the
5032    *          vamsasSeq array ordering, to preserve ordering of dataset
5033    */
5034   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5035           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5036   {
5037     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5038     // xRef Codon Maps
5039     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5040     boolean reorder = false;
5041     SequenceI dsq = null;
5042     if (sq != null && sq.getDatasetSequence() != null)
5043     {
5044       dsq = sq.getDatasetSequence();
5045     }
5046     else
5047     {
5048       reorder = true;
5049     }
5050     if (sq == null && ignoreUnrefed)
5051     {
5052       return;
5053     }
5054     String sqid = vamsasSeq.getDsseqid();
5055     if (dsq == null)
5056     {
5057       // need to create or add a new dataset sequence reference to this sequence
5058       if (sqid != null)
5059       {
5060         dsq = seqRefIds.get(sqid);
5061       }
5062       // check again
5063       if (dsq == null)
5064       {
5065         // make a new dataset sequence
5066         dsq = sq.createDatasetSequence();
5067         if (sqid == null)
5068         {
5069           // make up a new dataset reference for this sequence
5070           sqid = seqHash(dsq);
5071         }
5072         dsq.setVamsasId(uniqueSetSuffix + sqid);
5073         seqRefIds.put(sqid, dsq);
5074         if (ds == null)
5075         {
5076           if (dseqs != null)
5077           {
5078             dseqs.addElement(dsq);
5079           }
5080         }
5081         else
5082         {
5083           ds.addSequence(dsq);
5084         }
5085       }
5086       else
5087       {
5088         if (sq != dsq)
5089         { // make this dataset sequence sq's dataset sequence
5090           sq.setDatasetSequence(dsq);
5091           // and update the current dataset alignment
5092           if (ds == null)
5093           {
5094             if (dseqs != null)
5095             {
5096               if (!dseqs.contains(dsq))
5097               {
5098                 dseqs.add(dsq);
5099               }
5100             }
5101             else
5102             {
5103               if (ds.findIndex(dsq) < 0)
5104               {
5105                 ds.addSequence(dsq);
5106               }
5107             }
5108           }
5109         }
5110       }
5111     }
5112     // TODO: refactor this as a merge dataset sequence function
5113     // now check that sq (the dataset sequence) sequence really is the union of
5114     // all references to it
5115     // boolean pre = sq.getStart() < dsq.getStart();
5116     // boolean post = sq.getEnd() > dsq.getEnd();
5117     // if (pre || post)
5118     if (sq != dsq)
5119     {
5120       // StringBuffer sb = new StringBuffer();
5121       String newres = jalview.analysis.AlignSeq.extractGaps(
5122               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5123       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5124               && newres.length() > dsq.getLength())
5125       {
5126         // Update with the longer sequence.
5127         synchronized (dsq)
5128         {
5129           /*
5130            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5131            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5132            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5133            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5134            */
5135           dsq.setSequence(newres);
5136         }
5137         // TODO: merges will never happen if we 'know' we have the real dataset
5138         // sequence - this should be detected when id==dssid
5139         System.err.println(
5140                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5141         // + (pre ? "prepended" : "") + " "
5142         // + (post ? "appended" : ""));
5143       }
5144     }
5145     else
5146     {
5147       // sequence refs are identical. We may need to update the existing dataset
5148       // alignment with this one, though.
5149       if (ds != null && dseqs == null)
5150       {
5151         int opos = ds.findIndex(dsq);
5152         SequenceI tseq = null;
5153         if (opos != -1 && vseqpos != opos)
5154         {
5155           // remove from old position
5156           ds.deleteSequence(dsq);
5157         }
5158         if (vseqpos < ds.getHeight())
5159         {
5160           if (vseqpos != opos)
5161           {
5162             // save sequence at destination position
5163             tseq = ds.getSequenceAt(vseqpos);
5164             ds.replaceSequenceAt(vseqpos, dsq);
5165             ds.addSequence(tseq);
5166           }
5167         }
5168         else
5169         {
5170           ds.addSequence(dsq);
5171         }
5172       }
5173     }
5174   }
5175
5176   /*
5177    * TODO use AlignmentI here and in related methods - needs
5178    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5179    */
5180   Hashtable<String, AlignmentI> datasetIds = null;
5181
5182   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5183
5184   private AlignmentI getDatasetFor(String datasetId)
5185   {
5186     if (datasetIds == null)
5187     {
5188       datasetIds = new Hashtable<>();
5189       return null;
5190     }
5191     if (datasetIds.containsKey(datasetId))
5192     {
5193       return datasetIds.get(datasetId);
5194     }
5195     return null;
5196   }
5197
5198   private void addDatasetRef(String datasetId, AlignmentI dataset)
5199   {
5200     if (datasetIds == null)
5201     {
5202       datasetIds = new Hashtable<>();
5203     }
5204     datasetIds.put(datasetId, dataset);
5205   }
5206
5207   /**
5208    * make a new dataset ID for this jalview dataset alignment
5209    * 
5210    * @param dataset
5211    * @return
5212    */
5213   private String getDatasetIdRef(AlignmentI dataset)
5214   {
5215     if (dataset.getDataset() != null)
5216     {
5217       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5218     }
5219     String datasetId = makeHashCode(dataset, null);
5220     if (datasetId == null)
5221     {
5222       // make a new datasetId and record it
5223       if (dataset2Ids == null)
5224       {
5225         dataset2Ids = new IdentityHashMap<>();
5226       }
5227       else
5228       {
5229         datasetId = dataset2Ids.get(dataset);
5230       }
5231       if (datasetId == null)
5232       {
5233         datasetId = "ds" + dataset2Ids.size() + 1;
5234         dataset2Ids.put(dataset, datasetId);
5235       }
5236     }
5237     return datasetId;
5238   }
5239
5240   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5241   {
5242     for (int d = 0; d < sequence.getDBRefCount(); d++)
5243     {
5244       DBRef dr = sequence.getDBRef(d);
5245       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5246               sequence.getDBRef(d).getSource(),
5247               sequence.getDBRef(d).getVersion(),
5248               sequence.getDBRef(d).getAccessionId());
5249       if (dr.getMapping() != null)
5250       {
5251         entry.setMap(addMapping(dr.getMapping()));
5252       }
5253       datasetSequence.addDBRef(entry);
5254     }
5255   }
5256
5257   private jalview.datamodel.Mapping addMapping(Mapping m)
5258   {
5259     SequenceI dsto = null;
5260     // Mapping m = dr.getMapping();
5261     int fr[] = new int[m.getMapListFromCount() * 2];
5262     Enumeration f = m.enumerateMapListFrom();
5263     for (int _i = 0; f.hasMoreElements(); _i += 2)
5264     {
5265       MapListFrom mf = (MapListFrom) f.nextElement();
5266       fr[_i] = mf.getStart();
5267       fr[_i + 1] = mf.getEnd();
5268     }
5269     int fto[] = new int[m.getMapListToCount() * 2];
5270     f = m.enumerateMapListTo();
5271     for (int _i = 0; f.hasMoreElements(); _i += 2)
5272     {
5273       MapListTo mf = (MapListTo) f.nextElement();
5274       fto[_i] = mf.getStart();
5275       fto[_i + 1] = mf.getEnd();
5276     }
5277     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5278             fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
5279     if (m.getMappingChoice() != null)
5280     {
5281       MappingChoice mc = m.getMappingChoice();
5282       if (mc.getDseqFor() != null)
5283       {
5284         String dsfor = "" + mc.getDseqFor();
5285         if (seqRefIds.containsKey(dsfor))
5286         {
5287           /**
5288            * recover from hash
5289            */
5290           jmap.setTo(seqRefIds.get(dsfor));
5291         }
5292         else
5293         {
5294           frefedSequence.add(newMappingRef(dsfor, jmap));
5295         }
5296       }
5297       else
5298       {
5299         /**
5300          * local sequence definition
5301          */
5302         Sequence ms = mc.getSequence();
5303         SequenceI djs = null;
5304         String sqid = ms.getDsseqid();
5305         if (sqid != null && sqid.length() > 0)
5306         {
5307           /*
5308            * recover dataset sequence
5309            */
5310           djs = seqRefIds.get(sqid);
5311         }
5312         else
5313         {
5314           System.err.println(
5315                   "Warning - making up dataset sequence id for DbRef sequence map reference");
5316           sqid = ((Object) ms).toString(); // make up a new hascode for
5317           // undefined dataset sequence hash
5318           // (unlikely to happen)
5319         }
5320
5321         if (djs == null)
5322         {
5323           /**
5324            * make a new dataset sequence and add it to refIds hash
5325            */
5326           djs = new jalview.datamodel.Sequence(ms.getName(),
5327                   ms.getSequence());
5328           djs.setStart(jmap.getMap().getToLowest());
5329           djs.setEnd(jmap.getMap().getToHighest());
5330           djs.setVamsasId(uniqueSetSuffix + sqid);
5331           jmap.setTo(djs);
5332           incompleteSeqs.put(sqid, djs);
5333           seqRefIds.put(sqid, djs);
5334
5335         }
5336         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5337         addDBRefs(djs, ms);
5338
5339       }
5340     }
5341     return (jmap);
5342
5343   }
5344
5345   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5346           boolean keepSeqRefs)
5347   {
5348     initSeqRefs();
5349     JalviewModel jm = saveState(ap, null, null, null);
5350
5351     if (!keepSeqRefs)
5352     {
5353       clearSeqRefs();
5354       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5355     }
5356     else
5357     {
5358       uniqueSetSuffix = "";
5359       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5360       // overwrite the
5361       // view we just
5362       // copied
5363     }
5364     if (this.frefedSequence == null)
5365     {
5366       frefedSequence = new Vector();
5367     }
5368
5369     viewportsAdded.clear();
5370
5371     AlignFrame af = loadFromObject(jm, null, false, null);
5372     af.alignPanels.clear();
5373     af.closeMenuItem_actionPerformed(true);
5374
5375     /*
5376      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5377      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5378      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5379      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5380      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5381      */
5382
5383     return af.alignPanel;
5384   }
5385
5386   /**
5387    * flag indicating if hashtables should be cleared on finalization TODO this
5388    * flag may not be necessary
5389    */
5390   private final boolean _cleartables = true;
5391
5392   private Hashtable jvids2vobj;
5393
5394   /*
5395    * (non-Javadoc)
5396    * 
5397    * @see java.lang.Object#finalize()
5398    */
5399   @Override
5400   protected void finalize() throws Throwable
5401   {
5402     // really make sure we have no buried refs left.
5403     if (_cleartables)
5404     {
5405       clearSeqRefs();
5406     }
5407     this.seqRefIds = null;
5408     this.seqsToIds = null;
5409     super.finalize();
5410   }
5411
5412   private void warn(String msg)
5413   {
5414     warn(msg, null);
5415   }
5416
5417   private void warn(String msg, Exception e)
5418   {
5419     if (Cache.log != null)
5420     {
5421       if (e != null)
5422       {
5423         Cache.log.warn(msg, e);
5424       }
5425       else
5426       {
5427         Cache.log.warn(msg);
5428       }
5429     }
5430     else
5431     {
5432       System.err.println("Warning: " + msg);
5433       if (e != null)
5434       {
5435         e.printStackTrace();
5436       }
5437     }
5438   }
5439
5440   private void debug(String string)
5441   {
5442     debug(string, null);
5443   }
5444
5445   private void debug(String msg, Exception e)
5446   {
5447     if (Cache.log != null)
5448     {
5449       if (e != null)
5450       {
5451         Cache.log.debug(msg, e);
5452       }
5453       else
5454       {
5455         Cache.log.debug(msg);
5456       }
5457     }
5458     else
5459     {
5460       System.err.println("Warning: " + msg);
5461       if (e != null)
5462       {
5463         e.printStackTrace();
5464       }
5465     }
5466   }
5467
5468   /**
5469    * set the object to ID mapping tables used to write/recover objects and XML
5470    * ID strings for the jalview project. If external tables are provided then
5471    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5472    * object goes out of scope. - also populates the datasetIds hashtable with
5473    * alignment objects containing dataset sequences
5474    * 
5475    * @param vobj2jv
5476    *          Map from ID strings to jalview datamodel
5477    * @param jv2vobj
5478    *          Map from jalview datamodel to ID strings
5479    * 
5480    * 
5481    */
5482   public void setObjectMappingTables(Hashtable vobj2jv,
5483           IdentityHashMap jv2vobj)
5484   {
5485     this.jv2vobj = jv2vobj;
5486     this.vobj2jv = vobj2jv;
5487     Iterator ds = jv2vobj.keySet().iterator();
5488     String id;
5489     while (ds.hasNext())
5490     {
5491       Object jvobj = ds.next();
5492       id = jv2vobj.get(jvobj).toString();
5493       if (jvobj instanceof jalview.datamodel.Alignment)
5494       {
5495         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5496         {
5497           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5498         }
5499       }
5500       else if (jvobj instanceof jalview.datamodel.Sequence)
5501       {
5502         // register sequence object so the XML parser can recover it.
5503         if (seqRefIds == null)
5504         {
5505           seqRefIds = new HashMap<>();
5506         }
5507         if (seqsToIds == null)
5508         {
5509           seqsToIds = new IdentityHashMap<>();
5510         }
5511         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5512         seqsToIds.put((SequenceI) jvobj, id);
5513       }
5514       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5515       {
5516         String anid;
5517         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5518         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5519         if (jvann.annotationId == null)
5520         {
5521           jvann.annotationId = anid;
5522         }
5523         if (!jvann.annotationId.equals(anid))
5524         {
5525           // TODO verify that this is the correct behaviour
5526           this.warn("Overriding Annotation ID for " + anid
5527                   + " from different id : " + jvann.annotationId);
5528           jvann.annotationId = anid;
5529         }
5530       }
5531       else if (jvobj instanceof String)
5532       {
5533         if (jvids2vobj == null)
5534         {
5535           jvids2vobj = new Hashtable();
5536           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5537         }
5538       }
5539       else
5540       {
5541         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5542       }
5543     }
5544   }
5545
5546   /**
5547    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5548    * objects created from the project archive. If string is null (default for
5549    * construction) then suffix will be set automatically.
5550    * 
5551    * @param string
5552    */
5553   public void setUniqueSetSuffix(String string)
5554   {
5555     uniqueSetSuffix = string;
5556
5557   }
5558
5559   /**
5560    * uses skipList2 as the skipList for skipping views on sequence sets
5561    * associated with keys in the skipList
5562    * 
5563    * @param skipList2
5564    */
5565   public void setSkipList(Hashtable skipList2)
5566   {
5567     skipList = skipList2;
5568   }
5569
5570   /**
5571    * Reads the jar entry of given name and returns its contents, or null if the
5572    * entry is not found.
5573    * 
5574    * @param jprovider
5575    * @param jarEntryName
5576    * @return
5577    */
5578   protected String readJarEntry(jarInputStreamProvider jprovider,
5579           String jarEntryName)
5580   {
5581     String result = null;
5582     BufferedReader in = null;
5583
5584     try
5585     {
5586       /*
5587        * Reopen the jar input stream and traverse its entries to find a matching
5588        * name
5589        */
5590       JarInputStream jin = jprovider.getJarInputStream();
5591       JarEntry entry = null;
5592       do
5593       {
5594         entry = jin.getNextJarEntry();
5595       } while (entry != null && !entry.getName().equals(jarEntryName));
5596
5597       if (entry != null)
5598       {
5599         StringBuilder out = new StringBuilder(256);
5600         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5601         String data;
5602
5603         while ((data = in.readLine()) != null)
5604         {
5605           out.append(data);
5606         }
5607         result = out.toString();
5608       }
5609       else
5610       {
5611         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5612       }
5613     } catch (Exception ex)
5614     {
5615       ex.printStackTrace();
5616     } finally
5617     {
5618       if (in != null)
5619       {
5620         try
5621         {
5622           in.close();
5623         } catch (IOException e)
5624         {
5625           // ignore
5626         }
5627       }
5628     }
5629
5630     return result;
5631   }
5632
5633   /**
5634    * Returns an incrementing counter (0, 1, 2...)
5635    * 
5636    * @return
5637    */
5638   private synchronized int nextCounter()
5639   {
5640     return counter++;
5641   }
5642 }