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