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