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