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