JAL-2674 HiddenColumns interface tidies
[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           Iterator<int[]> hiddenRegions = hidden.iterator();
1421           while (hiddenRegions.hasNext())
1422           {
1423             int[] region = hiddenRegions.next();
1424             HiddenColumns hc = new HiddenColumns();
1425             hc.setStart(region[0]);
1426             hc.setEnd(region[1]);
1427             view.addHiddenColumns(hc);
1428           }
1429         }
1430       }
1431       if (calcIdSet.size() > 0)
1432       {
1433         for (String calcId : calcIdSet)
1434         {
1435           if (calcId.trim().length() > 0)
1436           {
1437             CalcIdParam cidp = createCalcIdParam(calcId, av);
1438             // Some calcIds have no parameters.
1439             if (cidp != null)
1440             {
1441               view.addCalcIdParam(cidp);
1442             }
1443           }
1444         }
1445       }
1446
1447       jms.addViewport(view);
1448     }
1449     object.setJalviewModelSequence(jms);
1450     object.getVamsasModel().addSequenceSet(vamsasSet);
1451
1452     if (jout != null && fileName != null)
1453     {
1454       // We may not want to write the object to disk,
1455       // eg we can copy the alignViewport to a new view object
1456       // using save and then load
1457       try
1458       {
1459         System.out.println("Writing jar entry " + fileName);
1460         JarEntry entry = new JarEntry(fileName);
1461         jout.putNextEntry(entry);
1462         PrintWriter pout = new PrintWriter(
1463                 new OutputStreamWriter(jout, UTF_8));
1464         Marshaller marshaller = new Marshaller(pout);
1465         marshaller.marshal(object);
1466         pout.flush();
1467         jout.closeEntry();
1468       } catch (Exception ex)
1469       {
1470         // TODO: raise error in GUI if marshalling failed.
1471         ex.printStackTrace();
1472       }
1473     }
1474     return object;
1475   }
1476
1477   /**
1478    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1479    * for each viewer, with
1480    * <ul>
1481    * <li>viewer geometry (position, size, split pane divider location)</li>
1482    * <li>index of the selected structure in the viewer (currently shows gapped
1483    * or ungapped)</li>
1484    * <li>the id of the annotation holding RNA secondary structure</li>
1485    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1486    * </ul>
1487    * Varna viewer state is also written out (in native Varna XML) to separate
1488    * project jar entries. A separate entry is written for each RNA structure
1489    * displayed, with the naming convention
1490    * <ul>
1491    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1492    * </ul>
1493    * 
1494    * @param jout
1495    * @param jseq
1496    * @param jds
1497    * @param viewIds
1498    * @param ap
1499    * @param storeDataset
1500    */
1501   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1502           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1503           boolean storeDataset)
1504   {
1505     if (Desktop.desktop == null)
1506     {
1507       return;
1508     }
1509     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1510     for (int f = frames.length - 1; f > -1; f--)
1511     {
1512       if (frames[f] instanceof AppVarna)
1513       {
1514         AppVarna varna = (AppVarna) frames[f];
1515         /*
1516          * link the sequence to every viewer that is showing it and is linked to
1517          * its alignment panel
1518          */
1519         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1520         {
1521           String viewId = varna.getViewId();
1522           RnaViewer rna = new RnaViewer();
1523           rna.setViewId(viewId);
1524           rna.setTitle(varna.getTitle());
1525           rna.setXpos(varna.getX());
1526           rna.setYpos(varna.getY());
1527           rna.setWidth(varna.getWidth());
1528           rna.setHeight(varna.getHeight());
1529           rna.setDividerLocation(varna.getDividerLocation());
1530           rna.setSelectedRna(varna.getSelectedIndex());
1531           jseq.addRnaViewer(rna);
1532
1533           /*
1534            * Store each Varna panel's state once in the project per sequence.
1535            * First time through only (storeDataset==false)
1536            */
1537           // boolean storeSessions = false;
1538           // String sequenceViewId = viewId + seqsToIds.get(jds);
1539           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1540           // {
1541           // viewIds.add(sequenceViewId);
1542           // storeSessions = true;
1543           // }
1544           for (RnaModel model : varna.getModels())
1545           {
1546             if (model.seq == jds)
1547             {
1548               /*
1549                * VARNA saves each view (sequence or alignment secondary
1550                * structure, gapped or trimmed) as a separate XML file
1551                */
1552               String jarEntryName = rnaSessions.get(model);
1553               if (jarEntryName == null)
1554               {
1555
1556                 String varnaStateFile = varna.getStateInfo(model.rna);
1557                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1558                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1559                 rnaSessions.put(model, jarEntryName);
1560               }
1561               SecondaryStructure ss = new SecondaryStructure();
1562               String annotationId = varna.getAnnotation(jds).annotationId;
1563               ss.setAnnotationId(annotationId);
1564               ss.setViewerState(jarEntryName);
1565               ss.setGapped(model.gapped);
1566               ss.setTitle(model.title);
1567               rna.addSecondaryStructure(ss);
1568             }
1569           }
1570         }
1571       }
1572     }
1573   }
1574
1575   /**
1576    * Copy the contents of a file to a new entry added to the output jar
1577    * 
1578    * @param jout
1579    * @param infilePath
1580    * @param jarEntryName
1581    */
1582   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1583           String jarEntryName)
1584   {
1585     DataInputStream dis = null;
1586     try
1587     {
1588       File file = new File(infilePath);
1589       if (file.exists() && jout != null)
1590       {
1591         dis = new DataInputStream(new FileInputStream(file));
1592         byte[] data = new byte[(int) file.length()];
1593         dis.readFully(data);
1594         writeJarEntry(jout, jarEntryName, data);
1595       }
1596     } catch (Exception ex)
1597     {
1598       ex.printStackTrace();
1599     } finally
1600     {
1601       if (dis != null)
1602       {
1603         try
1604         {
1605           dis.close();
1606         } catch (IOException e)
1607         {
1608           // ignore
1609         }
1610       }
1611     }
1612   }
1613
1614   /**
1615    * Write the data to a new entry of given name in the output jar file
1616    * 
1617    * @param jout
1618    * @param jarEntryName
1619    * @param data
1620    * @throws IOException
1621    */
1622   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
1623           byte[] data) throws IOException
1624   {
1625     if (jout != null)
1626     {
1627       System.out.println("Writing jar entry " + jarEntryName);
1628       jout.putNextEntry(new JarEntry(jarEntryName));
1629       DataOutputStream dout = new DataOutputStream(jout);
1630       dout.write(data, 0, data.length);
1631       dout.flush();
1632       jout.closeEntry();
1633     }
1634   }
1635
1636   /**
1637    * Save the state of a structure viewer
1638    * 
1639    * @param ap
1640    * @param jds
1641    * @param pdb
1642    *          the archive XML element under which to save the state
1643    * @param entry
1644    * @param viewIds
1645    * @param matchedFile
1646    * @param viewFrame
1647    * @return
1648    */
1649   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
1650           Pdbids pdb, PDBEntry entry, List<String> viewIds,
1651           String matchedFile, StructureViewerBase viewFrame)
1652   {
1653     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
1654
1655     /*
1656      * Look for any bindings for this viewer to the PDB file of interest
1657      * (including part matches excluding chain id)
1658      */
1659     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
1660     {
1661       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
1662       final String pdbId = pdbentry.getId();
1663       if (!pdbId.equals(entry.getId())
1664               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
1665                       .startsWith(pdbId.toLowerCase())))
1666       {
1667         /*
1668          * not interested in a binding to a different PDB entry here
1669          */
1670         continue;
1671       }
1672       if (matchedFile == null)
1673       {
1674         matchedFile = pdbentry.getFile();
1675       }
1676       else if (!matchedFile.equals(pdbentry.getFile()))
1677       {
1678         Cache.log.warn(
1679                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
1680                         + pdbentry.getFile());
1681       }
1682       // record the
1683       // file so we
1684       // can get at it if the ID
1685       // match is ambiguous (e.g.
1686       // 1QIP==1qipA)
1687
1688       for (int smap = 0; smap < viewFrame.getBinding()
1689               .getSequence()[peid].length; smap++)
1690       {
1691         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
1692         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
1693         {
1694           StructureState state = new StructureState();
1695           state.setVisible(true);
1696           state.setXpos(viewFrame.getX());
1697           state.setYpos(viewFrame.getY());
1698           state.setWidth(viewFrame.getWidth());
1699           state.setHeight(viewFrame.getHeight());
1700           final String viewId = viewFrame.getViewId();
1701           state.setViewId(viewId);
1702           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
1703           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
1704           state.setColourByJmol(viewFrame.isColouredByViewer());
1705           state.setType(viewFrame.getViewerType().toString());
1706           pdb.addStructureState(state);
1707         }
1708       }
1709     }
1710     return matchedFile;
1711   }
1712
1713   /**
1714    * Populates the AnnotationColours xml for save. This captures the settings of
1715    * the options in the 'Colour by Annotation' dialog.
1716    * 
1717    * @param acg
1718    * @param userColours
1719    * @param jms
1720    * @return
1721    */
1722   private AnnotationColours constructAnnotationColours(
1723           AnnotationColourGradient acg, List<UserColourScheme> userColours,
1724           JalviewModelSequence jms)
1725   {
1726     AnnotationColours ac = new AnnotationColours();
1727     ac.setAboveThreshold(acg.getAboveThreshold());
1728     ac.setThreshold(acg.getAnnotationThreshold());
1729     // 2.10.2 save annotationId (unique) not annotation label
1730     ac.setAnnotation(acg.getAnnotation().annotationId);
1731     if (acg.getBaseColour() instanceof UserColourScheme)
1732     {
1733       ac.setColourScheme(
1734               setUserColourScheme(acg.getBaseColour(), userColours, jms));
1735     }
1736     else
1737     {
1738       ac.setColourScheme(
1739               ColourSchemeProperty.getColourName(acg.getBaseColour()));
1740     }
1741
1742     ac.setMaxColour(acg.getMaxColour().getRGB());
1743     ac.setMinColour(acg.getMinColour().getRGB());
1744     ac.setPerSequence(acg.isSeqAssociated());
1745     ac.setPredefinedColours(acg.isPredefinedColours());
1746     return ac;
1747   }
1748
1749   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
1750           IdentityHashMap<SequenceGroup, String> groupRefs,
1751           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
1752           SequenceSet vamsasSet)
1753   {
1754
1755     for (int i = 0; i < aa.length; i++)
1756     {
1757       Annotation an = new Annotation();
1758
1759       AlignmentAnnotation annotation = aa[i];
1760       if (annotation.annotationId != null)
1761       {
1762         annotationIds.put(annotation.annotationId, annotation);
1763       }
1764
1765       an.setId(annotation.annotationId);
1766
1767       an.setVisible(annotation.visible);
1768
1769       an.setDescription(annotation.description);
1770
1771       if (annotation.sequenceRef != null)
1772       {
1773         // 2.9 JAL-1781 xref on sequence id rather than name
1774         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
1775       }
1776       if (annotation.groupRef != null)
1777       {
1778         String groupIdr = groupRefs.get(annotation.groupRef);
1779         if (groupIdr == null)
1780         {
1781           // make a locally unique String
1782           groupRefs.put(annotation.groupRef,
1783                   groupIdr = ("" + System.currentTimeMillis()
1784                           + annotation.groupRef.getName()
1785                           + groupRefs.size()));
1786         }
1787         an.setGroupRef(groupIdr.toString());
1788       }
1789
1790       // store all visualization attributes for annotation
1791       an.setGraphHeight(annotation.graphHeight);
1792       an.setCentreColLabels(annotation.centreColLabels);
1793       an.setScaleColLabels(annotation.scaleColLabel);
1794       an.setShowAllColLabels(annotation.showAllColLabels);
1795       an.setBelowAlignment(annotation.belowAlignment);
1796
1797       if (annotation.graph > 0)
1798       {
1799         an.setGraph(true);
1800         an.setGraphType(annotation.graph);
1801         an.setGraphGroup(annotation.graphGroup);
1802         if (annotation.getThreshold() != null)
1803         {
1804           ThresholdLine line = new ThresholdLine();
1805           line.setLabel(annotation.getThreshold().label);
1806           line.setValue(annotation.getThreshold().value);
1807           line.setColour(annotation.getThreshold().colour.getRGB());
1808           an.setThresholdLine(line);
1809         }
1810       }
1811       else
1812       {
1813         an.setGraph(false);
1814       }
1815
1816       an.setLabel(annotation.label);
1817
1818       if (annotation == av.getAlignmentQualityAnnot()
1819               || annotation == av.getAlignmentConservationAnnotation()
1820               || annotation == av.getAlignmentConsensusAnnotation()
1821               || annotation.autoCalculated)
1822       {
1823         // new way of indicating autocalculated annotation -
1824         an.setAutoCalculated(annotation.autoCalculated);
1825       }
1826       if (annotation.hasScore())
1827       {
1828         an.setScore(annotation.getScore());
1829       }
1830
1831       if (annotation.getCalcId() != null)
1832       {
1833         calcIdSet.add(annotation.getCalcId());
1834         an.setCalcId(annotation.getCalcId());
1835       }
1836       if (annotation.hasProperties())
1837       {
1838         for (String pr : annotation.getProperties())
1839         {
1840           Property prop = new Property();
1841           prop.setName(pr);
1842           prop.setValue(annotation.getProperty(pr));
1843           an.addProperty(prop);
1844         }
1845       }
1846
1847       AnnotationElement ae;
1848       if (annotation.annotations != null)
1849       {
1850         an.setScoreOnly(false);
1851         for (int a = 0; a < annotation.annotations.length; a++)
1852         {
1853           if ((annotation == null) || (annotation.annotations[a] == null))
1854           {
1855             continue;
1856           }
1857
1858           ae = new AnnotationElement();
1859           if (annotation.annotations[a].description != null)
1860           {
1861             ae.setDescription(annotation.annotations[a].description);
1862           }
1863           if (annotation.annotations[a].displayCharacter != null)
1864           {
1865             ae.setDisplayCharacter(
1866                     annotation.annotations[a].displayCharacter);
1867           }
1868
1869           if (!Float.isNaN(annotation.annotations[a].value))
1870           {
1871             ae.setValue(annotation.annotations[a].value);
1872           }
1873
1874           ae.setPosition(a);
1875           if (annotation.annotations[a].secondaryStructure > ' ')
1876           {
1877             ae.setSecondaryStructure(
1878                     annotation.annotations[a].secondaryStructure + "");
1879           }
1880
1881           if (annotation.annotations[a].colour != null
1882                   && annotation.annotations[a].colour != java.awt.Color.black)
1883           {
1884             ae.setColour(annotation.annotations[a].colour.getRGB());
1885           }
1886
1887           an.addAnnotationElement(ae);
1888           if (annotation.autoCalculated)
1889           {
1890             // only write one non-null entry into the annotation row -
1891             // sufficient to get the visualization attributes necessary to
1892             // display data
1893             continue;
1894           }
1895         }
1896       }
1897       else
1898       {
1899         an.setScoreOnly(true);
1900       }
1901       if (!storeDS || (storeDS && !annotation.autoCalculated))
1902       {
1903         // skip autocalculated annotation - these are only provided for
1904         // alignments
1905         vamsasSet.addAnnotation(an);
1906       }
1907     }
1908
1909   }
1910
1911   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
1912   {
1913     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
1914     if (settings != null)
1915     {
1916       CalcIdParam vCalcIdParam = new CalcIdParam();
1917       vCalcIdParam.setCalcId(calcId);
1918       vCalcIdParam.addServiceURL(settings.getServiceURI());
1919       // generic URI allowing a third party to resolve another instance of the
1920       // service used for this calculation
1921       for (String urls : settings.getServiceURLs())
1922       {
1923         vCalcIdParam.addServiceURL(urls);
1924       }
1925       vCalcIdParam.setVersion("1.0");
1926       if (settings.getPreset() != null)
1927       {
1928         WsParamSetI setting = settings.getPreset();
1929         vCalcIdParam.setName(setting.getName());
1930         vCalcIdParam.setDescription(setting.getDescription());
1931       }
1932       else
1933       {
1934         vCalcIdParam.setName("");
1935         vCalcIdParam.setDescription("Last used parameters");
1936       }
1937       // need to be able to recover 1) settings 2) user-defined presets or
1938       // recreate settings from preset 3) predefined settings provided by
1939       // service - or settings that can be transferred (or discarded)
1940       vCalcIdParam.setParameters(
1941               settings.getWsParamFile().replace("\n", "|\\n|"));
1942       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
1943       // todo - decide if updateImmediately is needed for any projects.
1944
1945       return vCalcIdParam;
1946     }
1947     return null;
1948   }
1949
1950   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
1951           AlignViewport av)
1952   {
1953     if (calcIdParam.getVersion().equals("1.0"))
1954     {
1955       Jws2Instance service = Jws2Discoverer.getDiscoverer()
1956               .getPreferredServiceFor(calcIdParam.getServiceURL());
1957       if (service != null)
1958       {
1959         WsParamSetI parmSet = null;
1960         try
1961         {
1962           parmSet = service.getParamStore().parseServiceParameterFile(
1963                   calcIdParam.getName(), calcIdParam.getDescription(),
1964                   calcIdParam.getServiceURL(),
1965                   calcIdParam.getParameters().replace("|\\n|", "\n"));
1966         } catch (IOException x)
1967         {
1968           warn("Couldn't parse parameter data for "
1969                   + calcIdParam.getCalcId(), x);
1970           return false;
1971         }
1972         List<ArgumentI> argList = null;
1973         if (calcIdParam.getName().length() > 0)
1974         {
1975           parmSet = service.getParamStore()
1976                   .getPreset(calcIdParam.getName());
1977           if (parmSet != null)
1978           {
1979             // TODO : check we have a good match with settings in AACon -
1980             // otherwise we'll need to create a new preset
1981           }
1982         }
1983         else
1984         {
1985           argList = parmSet.getArguments();
1986           parmSet = null;
1987         }
1988         AAConSettings settings = new AAConSettings(
1989                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
1990         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
1991                 calcIdParam.isNeedsUpdate());
1992         return true;
1993       }
1994       else
1995       {
1996         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
1997         return false;
1998       }
1999     }
2000     throw new Error(MessageManager.formatMessage(
2001             "error.unsupported_version_calcIdparam", new Object[]
2002             { calcIdParam.toString() }));
2003   }
2004
2005   /**
2006    * External mapping between jalview objects and objects yielding a valid and
2007    * unique object ID string. This is null for normal Jalview project IO, but
2008    * non-null when a jalview project is being read or written as part of a
2009    * vamsas session.
2010    */
2011   IdentityHashMap jv2vobj = null;
2012
2013   /**
2014    * Construct a unique ID for jvobj using either existing bindings or if none
2015    * exist, the result of the hashcode call for the object.
2016    * 
2017    * @param jvobj
2018    *          jalview data object
2019    * @return unique ID for referring to jvobj
2020    */
2021   private String makeHashCode(Object jvobj, String altCode)
2022   {
2023     if (jv2vobj != null)
2024     {
2025       Object id = jv2vobj.get(jvobj);
2026       if (id != null)
2027       {
2028         return id.toString();
2029       }
2030       // check string ID mappings
2031       if (jvids2vobj != null && jvobj instanceof String)
2032       {
2033         id = jvids2vobj.get(jvobj);
2034       }
2035       if (id != null)
2036       {
2037         return id.toString();
2038       }
2039       // give up and warn that something has gone wrong
2040       warn("Cannot find ID for object in external mapping : " + jvobj);
2041     }
2042     return altCode;
2043   }
2044
2045   /**
2046    * return local jalview object mapped to ID, if it exists
2047    * 
2048    * @param idcode
2049    *          (may be null)
2050    * @return null or object bound to idcode
2051    */
2052   private Object retrieveExistingObj(String idcode)
2053   {
2054     if (idcode != null && vobj2jv != null)
2055     {
2056       return vobj2jv.get(idcode);
2057     }
2058     return null;
2059   }
2060
2061   /**
2062    * binding from ID strings from external mapping table to jalview data model
2063    * objects.
2064    */
2065   private Hashtable vobj2jv;
2066
2067   private Sequence createVamsasSequence(String id, SequenceI jds)
2068   {
2069     return createVamsasSequence(true, id, jds, null);
2070   }
2071
2072   private Sequence createVamsasSequence(boolean recurse, String id,
2073           SequenceI jds, SequenceI parentseq)
2074   {
2075     Sequence vamsasSeq = new Sequence();
2076     vamsasSeq.setId(id);
2077     vamsasSeq.setName(jds.getName());
2078     vamsasSeq.setSequence(jds.getSequenceAsString());
2079     vamsasSeq.setDescription(jds.getDescription());
2080     jalview.datamodel.DBRefEntry[] dbrefs = null;
2081     if (jds.getDatasetSequence() != null)
2082     {
2083       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
2084     }
2085     else
2086     {
2087       // seqId==dsseqid so we can tell which sequences really are
2088       // dataset sequences only
2089       vamsasSeq.setDsseqid(id);
2090       dbrefs = jds.getDBRefs();
2091       if (parentseq == null)
2092       {
2093         parentseq = jds;
2094       }
2095     }
2096     if (dbrefs != null)
2097     {
2098       for (int d = 0; d < dbrefs.length; d++)
2099       {
2100         DBRef dbref = new DBRef();
2101         dbref.setSource(dbrefs[d].getSource());
2102         dbref.setVersion(dbrefs[d].getVersion());
2103         dbref.setAccessionId(dbrefs[d].getAccessionId());
2104         if (dbrefs[d].hasMap())
2105         {
2106           Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
2107                   jds, recurse);
2108           dbref.setMapping(mp);
2109         }
2110         vamsasSeq.addDBRef(dbref);
2111       }
2112     }
2113     return vamsasSeq;
2114   }
2115
2116   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
2117           SequenceI parentseq, SequenceI jds, boolean recurse)
2118   {
2119     Mapping mp = null;
2120     if (jmp.getMap() != null)
2121     {
2122       mp = new Mapping();
2123
2124       jalview.util.MapList mlst = jmp.getMap();
2125       List<int[]> r = mlst.getFromRanges();
2126       for (int[] range : r)
2127       {
2128         MapListFrom mfrom = new MapListFrom();
2129         mfrom.setStart(range[0]);
2130         mfrom.setEnd(range[1]);
2131         mp.addMapListFrom(mfrom);
2132       }
2133       r = mlst.getToRanges();
2134       for (int[] range : r)
2135       {
2136         MapListTo mto = new MapListTo();
2137         mto.setStart(range[0]);
2138         mto.setEnd(range[1]);
2139         mp.addMapListTo(mto);
2140       }
2141       mp.setMapFromUnit(mlst.getFromRatio());
2142       mp.setMapToUnit(mlst.getToRatio());
2143       if (jmp.getTo() != null)
2144       {
2145         MappingChoice mpc = new MappingChoice();
2146
2147         // check/create ID for the sequence referenced by getTo()
2148
2149         String jmpid = "";
2150         SequenceI ps = null;
2151         if (parentseq != jmp.getTo()
2152                 && parentseq.getDatasetSequence() != jmp.getTo())
2153         {
2154           // chaining dbref rather than a handshaking one
2155           jmpid = seqHash(ps = jmp.getTo());
2156         }
2157         else
2158         {
2159           jmpid = seqHash(ps = parentseq);
2160         }
2161         mpc.setDseqFor(jmpid);
2162         if (!seqRefIds.containsKey(mpc.getDseqFor()))
2163         {
2164           jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2165           seqRefIds.put(mpc.getDseqFor(), ps);
2166         }
2167         else
2168         {
2169           jalview.bin.Cache.log.debug("reusing DseqFor ID");
2170         }
2171
2172         mp.setMappingChoice(mpc);
2173       }
2174     }
2175     return mp;
2176   }
2177
2178   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2179           List<UserColourScheme> userColours, JalviewModelSequence jms)
2180   {
2181     String id = null;
2182     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2183     boolean newucs = false;
2184     if (!userColours.contains(ucs))
2185     {
2186       userColours.add(ucs);
2187       newucs = true;
2188     }
2189     id = "ucs" + userColours.indexOf(ucs);
2190     if (newucs)
2191     {
2192       // actually create the scheme's entry in the XML model
2193       java.awt.Color[] colours = ucs.getColours();
2194       jalview.schemabinding.version2.UserColours uc = new jalview.schemabinding.version2.UserColours();
2195       jalview.schemabinding.version2.UserColourScheme jbucs = new jalview.schemabinding.version2.UserColourScheme();
2196
2197       for (int i = 0; i < colours.length; i++)
2198       {
2199         jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2200         col.setName(ResidueProperties.aa[i]);
2201         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2202         jbucs.addColour(col);
2203       }
2204       if (ucs.getLowerCaseColours() != null)
2205       {
2206         colours = ucs.getLowerCaseColours();
2207         for (int i = 0; i < colours.length; i++)
2208         {
2209           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2210           col.setName(ResidueProperties.aa[i].toLowerCase());
2211           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2212           jbucs.addColour(col);
2213         }
2214       }
2215
2216       uc.setId(id);
2217       uc.setUserColourScheme(jbucs);
2218       jms.addUserColours(uc);
2219     }
2220
2221     return id;
2222   }
2223
2224   jalview.schemes.UserColourScheme getUserColourScheme(
2225           JalviewModelSequence jms, String id)
2226   {
2227     UserColours[] uc = jms.getUserColours();
2228     UserColours colours = null;
2229
2230     for (int i = 0; i < uc.length; i++)
2231     {
2232       if (uc[i].getId().equals(id))
2233       {
2234         colours = uc[i];
2235
2236         break;
2237       }
2238     }
2239
2240     java.awt.Color[] newColours = new java.awt.Color[24];
2241
2242     for (int i = 0; i < 24; i++)
2243     {
2244       newColours[i] = new java.awt.Color(Integer.parseInt(
2245               colours.getUserColourScheme().getColour(i).getRGB(), 16));
2246     }
2247
2248     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2249             newColours);
2250
2251     if (colours.getUserColourScheme().getColourCount() > 24)
2252     {
2253       newColours = new java.awt.Color[23];
2254       for (int i = 0; i < 23; i++)
2255       {
2256         newColours[i] = new java.awt.Color(Integer.parseInt(
2257                 colours.getUserColourScheme().getColour(i + 24).getRGB(),
2258                 16));
2259       }
2260       ucs.setLowerCaseColours(newColours);
2261     }
2262
2263     return ucs;
2264   }
2265
2266   /**
2267    * contains last error message (if any) encountered by XML loader.
2268    */
2269   String errorMessage = null;
2270
2271   /**
2272    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2273    * exceptions are raised during project XML parsing
2274    */
2275   public boolean attemptversion1parse = true;
2276
2277   /**
2278    * Load a jalview project archive from a jar file
2279    * 
2280    * @param file
2281    *          - HTTP URL or filename
2282    */
2283   public AlignFrame loadJalviewAlign(final String file)
2284   {
2285
2286     jalview.gui.AlignFrame af = null;
2287
2288     try
2289     {
2290       // create list to store references for any new Jmol viewers created
2291       newStructureViewers = new Vector<>();
2292       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2293       // Workaround is to make sure caller implements the JarInputStreamProvider
2294       // interface
2295       // so we can re-open the jar input stream for each entry.
2296
2297       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2298       af = loadJalviewAlign(jprovider);
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 }