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