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