Merge branch 'develop' of https://source.jalview.org/git/jalview.git into kjvdh/featu...
[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", "aptx-test");
1119
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     String treeFile = copyJarEntry(jprovider, "aptx-test", "aptx", null);
3578     if (treeFile != null)
3579     {
3580     try
3581     {
3582       AptxInit.createInstancesFromFile(treeFile, av);
3583     } catch (IOException e)
3584     {
3585       // TODO Auto-generated catch block
3586       e.printStackTrace();
3587       }
3588     }
3589     
3590   }
3591
3592
3593
3594   /**
3595    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3596    * panel is restored from separate jar entries, two (gapped and trimmed) per
3597    * sequence and secondary structure.
3598    * 
3599    * Currently each viewer shows just one sequence and structure (gapped and
3600    * trimmed), however this method is designed to support multiple sequences or
3601    * structures in viewers if wanted in future.
3602    * 
3603    * @param jprovider
3604    * @param jseqs
3605    * @param ap
3606    */
3607   private void loadRnaViewers(jarInputStreamProvider jprovider,
3608           JSeq[] jseqs, AlignmentPanel ap)
3609   {
3610     /*
3611      * scan the sequences for references to viewers; create each one the first
3612      * time it is referenced, add Rna models to existing viewers
3613      */
3614     for (JSeq jseq : jseqs)
3615     {
3616       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3617       {
3618         RnaViewer viewer = jseq.getRnaViewer(i);
3619         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
3620                 ap);
3621
3622         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3623         {
3624           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3625           SequenceI seq = seqRefIds.get(jseq.getId());
3626           AlignmentAnnotation ann = this.annotationIds
3627                   .get(ss.getAnnotationId());
3628
3629           /*
3630            * add the structure to the Varna display (with session state copied
3631            * from the jar to a temporary file)
3632            */
3633           boolean gapped = ss.isGapped();
3634           String rnaTitle = ss.getTitle();
3635           String sessionState = ss.getViewerState();
3636           String tempStateFile = copyJarEntry(jprovider, sessionState,
3637                   "varna", null);
3638           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3639           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3640         }
3641         appVarna.setInitialSelection(viewer.getSelectedRna());
3642       }
3643     }
3644   }
3645
3646   /**
3647    * Locate and return an already instantiated matching AppVarna, or create one
3648    * if not found
3649    * 
3650    * @param viewer
3651    * @param viewIdSuffix
3652    * @param ap
3653    * @return
3654    */
3655   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3656           String viewIdSuffix, AlignmentPanel ap)
3657   {
3658     /*
3659      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3660      * if load is repeated
3661      */
3662     String postLoadId = viewer.getViewId() + viewIdSuffix;
3663     for (JInternalFrame frame : getAllFrames())
3664     {
3665       if (frame instanceof AppVarna)
3666       {
3667         AppVarna varna = (AppVarna) frame;
3668         if (postLoadId.equals(varna.getViewId()))
3669         {
3670           // this viewer is already instantiated
3671           // could in future here add ap as another 'parent' of the
3672           // AppVarna window; currently just 1-to-many
3673           return varna;
3674         }
3675       }
3676     }
3677
3678     /*
3679      * viewer not found - make it
3680      */
3681     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
3682             viewer.getXpos(), viewer.getYpos(), viewer.getWidth(),
3683             viewer.getHeight(), viewer.getDividerLocation());
3684     AppVarna varna = new AppVarna(model, ap);
3685
3686     return varna;
3687   }
3688
3689   /**
3690    * Load any saved trees
3691    * 
3692    * @param jms
3693    * @param view
3694    * @param af
3695    * @param av
3696    * @param ap
3697    */
3698   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3699           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3700   {
3701     // TODO result of automated refactoring - are all these parameters needed?
3702     try
3703     {
3704       for (int t = 0; t < jms.getTreeCount(); t++)
3705       {
3706
3707         Tree tree = jms.getTree(t);
3708         NewickFile newick = new jalview.io.NewickFile(tree.getNewick());
3709
3710         TreeFrameI externalViewer = AptxInit.createInstanceFromNhx(
3711                 tree.getTitle(), tree.getNewick(),
3712                 av);
3713
3714
3715         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3716         if (tp == null)
3717         {
3718
3719
3720           tp = af.showNewickTree(
3721                   newick,
3722                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3723                   tree.getXpos(), tree.getYpos());
3724           if (tree.getId() != null)
3725           {
3726             // perhaps bind the tree id to something ?
3727           }
3728         }
3729         else
3730         {
3731           // update local tree attributes ?
3732           // TODO: should check if tp has been manipulated by user - if so its
3733           // settings shouldn't be modified
3734           tp.setTitle(tree.getTitle());
3735           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(),
3736                   tree.getWidth(), tree.getHeight()));
3737           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3738           // views'
3739           // works still
3740           tp.treeCanvas.av = av; // af.viewport;
3741           tp.treeCanvas.ap = ap; // af.alignPanel;
3742
3743         }
3744         if (tp == null)
3745         {
3746           warn("There was a problem recovering stored Newick tree: \n"
3747                   + tree.getNewick());
3748           continue;
3749         }
3750
3751         tp.fitToWindow.setState(tree.getFitToWindow());
3752         tp.fitToWindow_actionPerformed(null);
3753
3754         if (tree.getFontName() != null)
3755         {
3756           tp.setTreeFont(new java.awt.Font(tree.getFontName(),
3757                   tree.getFontStyle(), tree.getFontSize()));
3758         }
3759         else
3760         {
3761           tp.setTreeFont(new java.awt.Font(view.getFontName(),
3762                   view.getFontStyle(), tree.getFontSize()));
3763         }
3764
3765         tp.showPlaceholders(tree.getMarkUnlinked());
3766         tp.showBootstrap(tree.getShowBootstrap());
3767         tp.showDistances(tree.getShowDistances());
3768
3769         tp.treeCanvas.threshold = tree.getThreshold();
3770
3771         if (tree.getCurrentTree())
3772         {
3773           af.viewport.setCurrentTree(tp.getTree());
3774         }
3775       }
3776
3777     } catch (Exception ex)
3778     {
3779       ex.printStackTrace();
3780     }
3781   }
3782
3783   /**
3784    * Load and link any saved structure viewers.
3785    * 
3786    * @param jprovider
3787    * @param jseqs
3788    * @param af
3789    * @param ap
3790    */
3791   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3792           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3793   {
3794     /*
3795      * Run through all PDB ids on the alignment, and collect mappings between
3796      * distinct view ids and all sequences referring to that view.
3797      */
3798     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
3799
3800     for (int i = 0; i < jseqs.length; i++)
3801     {
3802       if (jseqs[i].getPdbidsCount() > 0)
3803       {
3804         Pdbids[] ids = jseqs[i].getPdbids();
3805         for (int p = 0; p < ids.length; p++)
3806         {
3807           final int structureStateCount = ids[p].getStructureStateCount();
3808           for (int s = 0; s < structureStateCount; s++)
3809           {
3810             // check to see if we haven't already created this structure view
3811             final StructureState structureState = ids[p]
3812                     .getStructureState(s);
3813             String sviewid = (structureState.getViewId() == null) ? null
3814                     : structureState.getViewId() + uniqueSetSuffix;
3815             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3816             // Originally : ids[p].getFile()
3817             // : TODO: verify external PDB file recovery still works in normal
3818             // jalview project load
3819             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId(),
3820                     ids[p].getFile()));
3821             jpdb.setId(ids[p].getId());
3822
3823             int x = structureState.getXpos();
3824             int y = structureState.getYpos();
3825             int width = structureState.getWidth();
3826             int height = structureState.getHeight();
3827
3828             // Probably don't need to do this anymore...
3829             // Desktop.desktop.getComponentAt(x, y);
3830             // TODO: NOW: check that this recovers the PDB file correctly.
3831             String pdbFile = loadPDBFile(jprovider, ids[p].getId(),
3832                     ids[p].getFile());
3833             jalview.datamodel.SequenceI seq = seqRefIds
3834                     .get(jseqs[i].getId() + "");
3835             if (sviewid == null)
3836             {
3837               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
3838                       + height;
3839             }
3840             if (!structureViewers.containsKey(sviewid))
3841             {
3842               structureViewers.put(sviewid,
3843                       new StructureViewerModel(x, y, width, height, false,
3844                               false, true, structureState.getViewId(),
3845                               structureState.getType()));
3846               // Legacy pre-2.7 conversion JAL-823 :
3847               // do not assume any view has to be linked for colour by
3848               // sequence
3849             }
3850
3851             // assemble String[] { pdb files }, String[] { id for each
3852             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3853             // seqs_file 2}, boolean[] {
3854             // linkAlignPanel,superposeWithAlignpanel}} from hash
3855             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3856             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3857                     | (structureState.hasAlignwithAlignPanel()
3858                             ? structureState.getAlignwithAlignPanel()
3859                             : false));
3860
3861             /*
3862              * Default colour by linked panel to false if not specified (e.g.
3863              * for pre-2.7 projects)
3864              */
3865             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3866             colourWithAlignPanel |= (structureState
3867                     .hasColourwithAlignPanel()
3868                             ? structureState.getColourwithAlignPanel()
3869                             : false);
3870             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3871
3872             /*
3873              * Default colour by viewer to true if not specified (e.g. for
3874              * pre-2.7 projects)
3875              */
3876             boolean colourByViewer = jmoldat.isColourByViewer();
3877             colourByViewer &= structureState.hasColourByJmol()
3878                     ? structureState.getColourByJmol()
3879                     : true;
3880             jmoldat.setColourByViewer(colourByViewer);
3881
3882             if (jmoldat.getStateData().length() < structureState
3883                     .getContent().length())
3884             {
3885               {
3886                 jmoldat.setStateData(structureState.getContent());
3887               }
3888             }
3889             if (ids[p].getFile() != null)
3890             {
3891               File mapkey = new File(ids[p].getFile());
3892               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3893               if (seqstrmaps == null)
3894               {
3895                 jmoldat.getFileData().put(mapkey,
3896                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3897                                 ids[p].getId()));
3898               }
3899               if (!seqstrmaps.getSeqList().contains(seq))
3900               {
3901                 seqstrmaps.getSeqList().add(seq);
3902                 // TODO and chains?
3903               }
3904             }
3905             else
3906             {
3907               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");
3908               warn(errorMessage);
3909             }
3910           }
3911         }
3912       }
3913     }
3914     // Instantiate the associated structure views
3915     for (Entry<String, StructureViewerModel> entry : structureViewers
3916             .entrySet())
3917     {
3918       try
3919       {
3920         createOrLinkStructureViewer(entry, af, ap, jprovider);
3921       } catch (Exception e)
3922       {
3923         System.err.println(
3924                 "Error loading structure viewer: " + e.getMessage());
3925         // failed - try the next one
3926       }
3927     }
3928   }
3929
3930   /**
3931    * 
3932    * @param viewerData
3933    * @param af
3934    * @param ap
3935    * @param jprovider
3936    */
3937   protected void createOrLinkStructureViewer(
3938           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3939           AlignmentPanel ap, jarInputStreamProvider jprovider)
3940   {
3941     final StructureViewerModel stateData = viewerData.getValue();
3942
3943     /*
3944      * Search for any viewer windows already open from other alignment views
3945      * that exactly match the stored structure state
3946      */
3947     StructureViewerBase comp = findMatchingViewer(viewerData);
3948
3949     if (comp != null)
3950     {
3951       linkStructureViewer(ap, comp, stateData);
3952       return;
3953     }
3954
3955     /*
3956      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3957      * "viewer_"+stateData.viewId
3958      */
3959     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3960     {
3961       createChimeraViewer(viewerData, af, jprovider);
3962     }
3963     else
3964     {
3965       /*
3966        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3967        */
3968       createJmolViewer(viewerData, af, jprovider);
3969     }
3970   }
3971
3972   /**
3973    * Create a new Chimera viewer.
3974    * 
3975    * @param data
3976    * @param af
3977    * @param jprovider
3978    */
3979   protected void createChimeraViewer(
3980           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3981           jarInputStreamProvider jprovider)
3982   {
3983     StructureViewerModel data = viewerData.getValue();
3984     String chimeraSessionFile = data.getStateData();
3985
3986     /*
3987      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3988      * 
3989      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3990      * 'uniquified' sviewid used to reconstruct the viewer here
3991      */
3992     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3993     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3994             "chimera", null);
3995
3996     Set<Entry<File, StructureData>> fileData = data.getFileData()
3997             .entrySet();
3998     List<PDBEntry> pdbs = new ArrayList<>();
3999     List<SequenceI[]> allseqs = new ArrayList<>();
4000     for (Entry<File, StructureData> pdb : fileData)
4001     {
4002       String filePath = pdb.getValue().getFilePath();
4003       String pdbId = pdb.getValue().getPdbId();
4004       // pdbs.add(new PDBEntry(filePath, pdbId));
4005       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4006       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4007       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4008       allseqs.add(seqs);
4009     }
4010
4011     boolean colourByChimera = data.isColourByViewer();
4012     boolean colourBySequence = data.isColourWithAlignPanel();
4013
4014     // TODO use StructureViewer as a factory here, see JAL-1761
4015     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4016     final SequenceI[][] seqsArray = allseqs
4017             .toArray(new SequenceI[allseqs.size()][]);
4018     String newViewId = viewerData.getKey();
4019
4020     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4021             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4022             colourBySequence, newViewId);
4023     cvf.setSize(data.getWidth(), data.getHeight());
4024     cvf.setLocation(data.getX(), data.getY());
4025   }
4026
4027   /**
4028    * Create a new Jmol window. First parse the Jmol state to translate filenames
4029    * loaded into the view, and record the order in which files are shown in the
4030    * Jmol view, so we can add the sequence mappings in same order.
4031    * 
4032    * @param viewerData
4033    * @param af
4034    * @param jprovider
4035    */
4036   protected void createJmolViewer(
4037           final Entry<String, StructureViewerModel> viewerData,
4038           AlignFrame af, jarInputStreamProvider jprovider)
4039   {
4040     final StructureViewerModel svattrib = viewerData.getValue();
4041     String state = svattrib.getStateData();
4042
4043     /*
4044      * Pre-2.9: state element value is the Jmol state string
4045      * 
4046      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4047      * + viewId
4048      */
4049     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4050     {
4051       state = readJarEntry(jprovider,
4052               getViewerJarEntryName(svattrib.getViewId()));
4053     }
4054
4055     List<String> pdbfilenames = new ArrayList<>();
4056     List<SequenceI[]> seqmaps = new ArrayList<>();
4057     List<String> pdbids = new ArrayList<>();
4058     StringBuilder newFileLoc = new StringBuilder(64);
4059     int cp = 0, ncp, ecp;
4060     Map<File, StructureData> oldFiles = svattrib.getFileData();
4061     while ((ncp = state.indexOf("load ", cp)) > -1)
4062     {
4063       do
4064       {
4065         // look for next filename in load statement
4066         newFileLoc.append(state.substring(cp,
4067                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4068         String oldfilenam = state.substring(ncp,
4069                 ecp = state.indexOf("\"", ncp));
4070         // recover the new mapping data for this old filename
4071         // have to normalize filename - since Jmol and jalview do
4072         // filename
4073         // translation differently.
4074         StructureData filedat = oldFiles.get(new File(oldfilenam));
4075         if (filedat == null)
4076         {
4077           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4078           filedat = oldFiles.get(new File(reformatedOldFilename));
4079         }
4080         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4081         pdbfilenames.add(filedat.getFilePath());
4082         pdbids.add(filedat.getPdbId());
4083         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4084         newFileLoc.append("\"");
4085         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4086                       // look for next file statement.
4087       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4088     }
4089     if (cp > 0)
4090     {
4091       // just append rest of state
4092       newFileLoc.append(state.substring(cp));
4093     }
4094     else
4095     {
4096       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4097       newFileLoc = new StringBuilder(state);
4098       newFileLoc.append("; load append ");
4099       for (File id : oldFiles.keySet())
4100       {
4101         // add this and any other pdb files that should be present in
4102         // the viewer
4103         StructureData filedat = oldFiles.get(id);
4104         newFileLoc.append(filedat.getFilePath());
4105         pdbfilenames.add(filedat.getFilePath());
4106         pdbids.add(filedat.getPdbId());
4107         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4108         newFileLoc.append(" \"");
4109         newFileLoc.append(filedat.getFilePath());
4110         newFileLoc.append("\"");
4111
4112       }
4113       newFileLoc.append(";");
4114     }
4115
4116     if (newFileLoc.length() == 0)
4117     {
4118       return;
4119     }
4120     int histbug = newFileLoc.indexOf("history = ");
4121     if (histbug > -1)
4122     {
4123       /*
4124        * change "history = [true|false];" to "history = [1|0];"
4125        */
4126       histbug += 10;
4127       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4128       String val = (diff == -1) ? null
4129               : newFileLoc.substring(histbug, diff);
4130       if (val != null && val.length() >= 4)
4131       {
4132         if (val.contains("e")) // eh? what can it be?
4133         {
4134           if (val.trim().equals("true"))
4135           {
4136             val = "1";
4137           }
4138           else
4139           {
4140             val = "0";
4141           }
4142           newFileLoc.replace(histbug, diff, val);
4143         }
4144       }
4145     }
4146
4147     final String[] pdbf = pdbfilenames
4148             .toArray(new String[pdbfilenames.size()]);
4149     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4150     final SequenceI[][] sq = seqmaps
4151             .toArray(new SequenceI[seqmaps.size()][]);
4152     final String fileloc = newFileLoc.toString();
4153     final String sviewid = viewerData.getKey();
4154     final AlignFrame alf = af;
4155     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4156             svattrib.getWidth(), svattrib.getHeight());
4157     try
4158     {
4159       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4160       {
4161         @Override
4162         public void run()
4163         {
4164           JalviewStructureDisplayI sview = null;
4165           try
4166           {
4167             sview = new StructureViewer(
4168                     alf.alignPanel.getStructureSelectionManager())
4169                             .createView(StructureViewer.ViewerType.JMOL,
4170                                     pdbf, id, sq, alf.alignPanel, svattrib,
4171                                     fileloc, rect, sviewid);
4172             addNewStructureViewer(sview);
4173           } catch (OutOfMemoryError ex)
4174           {
4175             new OOMWarning("restoring structure view for PDB id " + id,
4176                     (OutOfMemoryError) ex.getCause());
4177             if (sview != null && sview.isVisible())
4178             {
4179               sview.closeViewer(false);
4180               sview.setVisible(false);
4181               sview.dispose();
4182             }
4183           }
4184         }
4185       });
4186     } catch (InvocationTargetException ex)
4187     {
4188       warn("Unexpected error when opening Jmol view.", ex);
4189
4190     } catch (InterruptedException e)
4191     {
4192       // e.printStackTrace();
4193     }
4194
4195   }
4196
4197   /**
4198    * Generates a name for the entry in the project jar file to hold state
4199    * information for a structure viewer
4200    * 
4201    * @param viewId
4202    * @return
4203    */
4204   protected String getViewerJarEntryName(String viewId)
4205   {
4206     return VIEWER_PREFIX + viewId;
4207   }
4208
4209   /**
4210    * Returns any open frame that matches given structure viewer data. The match
4211    * is based on the unique viewId, or (for older project versions) the frame's
4212    * geometry.
4213    * 
4214    * @param viewerData
4215    * @return
4216    */
4217   protected StructureViewerBase findMatchingViewer(
4218           Entry<String, StructureViewerModel> viewerData)
4219   {
4220     final String sviewid = viewerData.getKey();
4221     final StructureViewerModel svattrib = viewerData.getValue();
4222     StructureViewerBase comp = null;
4223     JInternalFrame[] frames = getAllFrames();
4224     for (JInternalFrame frame : frames)
4225     {
4226       if (frame instanceof StructureViewerBase)
4227       {
4228         /*
4229          * Post jalview 2.4 schema includes structure view id
4230          */
4231         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4232                 .equals(sviewid))
4233         {
4234           comp = (StructureViewerBase) frame;
4235           break; // break added in 2.9
4236         }
4237         /*
4238          * Otherwise test for matching position and size of viewer frame
4239          */
4240         else if (frame.getX() == svattrib.getX()
4241                 && frame.getY() == svattrib.getY()
4242                 && frame.getHeight() == svattrib.getHeight()
4243                 && frame.getWidth() == svattrib.getWidth())
4244         {
4245           comp = (StructureViewerBase) frame;
4246           // no break in faint hope of an exact match on viewId
4247         }
4248       }
4249     }
4250     return comp;
4251   }
4252
4253   /**
4254    * Link an AlignmentPanel to an existing structure viewer.
4255    * 
4256    * @param ap
4257    * @param viewer
4258    * @param oldFiles
4259    * @param useinViewerSuperpos
4260    * @param usetoColourbyseq
4261    * @param viewerColouring
4262    */
4263   protected void linkStructureViewer(AlignmentPanel ap,
4264           StructureViewerBase viewer, StructureViewerModel stateData)
4265   {
4266     // NOTE: if the jalview project is part of a shared session then
4267     // view synchronization should/could be done here.
4268
4269     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4270     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4271     final boolean viewerColouring = stateData.isColourByViewer();
4272     Map<File, StructureData> oldFiles = stateData.getFileData();
4273
4274     /*
4275      * Add mapping for sequences in this view to an already open viewer
4276      */
4277     final AAStructureBindingModel binding = viewer.getBinding();
4278     for (File id : oldFiles.keySet())
4279     {
4280       // add this and any other pdb files that should be present in the
4281       // viewer
4282       StructureData filedat = oldFiles.get(id);
4283       String pdbFile = filedat.getFilePath();
4284       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4285       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4286               null);
4287       binding.addSequenceForStructFile(pdbFile, seq);
4288     }
4289     // and add the AlignmentPanel's reference to the view panel
4290     viewer.addAlignmentPanel(ap);
4291     if (useinViewerSuperpos)
4292     {
4293       viewer.useAlignmentPanelForSuperposition(ap);
4294     }
4295     else
4296     {
4297       viewer.excludeAlignmentPanelForSuperposition(ap);
4298     }
4299     if (usetoColourbyseq)
4300     {
4301       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4302     }
4303     else
4304     {
4305       viewer.excludeAlignmentPanelForColourbyseq(ap);
4306     }
4307   }
4308
4309   /**
4310    * Get all frames within the Desktop.
4311    * 
4312    * @return
4313    */
4314   protected JInternalFrame[] getAllFrames()
4315   {
4316     JInternalFrame[] frames = null;
4317     // TODO is this necessary - is it safe - risk of hanging?
4318     do
4319     {
4320       try
4321       {
4322         frames = Desktop.desktop.getAllFrames();
4323       } catch (ArrayIndexOutOfBoundsException e)
4324       {
4325         // occasional No such child exceptions are thrown here...
4326         try
4327         {
4328           Thread.sleep(10);
4329         } catch (InterruptedException f)
4330         {
4331         }
4332       }
4333     } while (frames == null);
4334     return frames;
4335   }
4336
4337   /**
4338    * Answers true if 'version' is equal to or later than 'supported', where each
4339    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4340    * changes. Development and test values for 'version' are leniently treated
4341    * i.e. answer true.
4342    * 
4343    * @param supported
4344    *          - minimum version we are comparing against
4345    * @param version
4346    *          - version of data being processsed
4347    * @return
4348    */
4349   public static boolean isVersionStringLaterThan(String supported,
4350           String version)
4351   {
4352     if (supported == null || version == null
4353             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4354             || version.equalsIgnoreCase("Test")
4355             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4356     {
4357       System.err.println("Assuming project file with "
4358               + (version == null ? "null" : version)
4359               + " is compatible with Jalview version " + supported);
4360       return true;
4361     }
4362     else
4363     {
4364       return StringUtils.compareVersions(version, supported, "b") >= 0;
4365     }
4366   }
4367
4368   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4369
4370   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4371   {
4372     if (newStructureViewers != null)
4373     {
4374       sview.getBinding().setFinishedLoadingFromArchive(false);
4375       newStructureViewers.add(sview);
4376     }
4377   }
4378
4379   protected void setLoadingFinishedForNewStructureViewers()
4380   {
4381     if (newStructureViewers != null)
4382     {
4383       for (JalviewStructureDisplayI sview : newStructureViewers)
4384       {
4385         sview.getBinding().setFinishedLoadingFromArchive(true);
4386       }
4387       newStructureViewers.clear();
4388       newStructureViewers = null;
4389     }
4390   }
4391
4392   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4393           List<SequenceI> hiddenSeqs, AlignmentI al,
4394           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4395           String viewId, List<JvAnnotRow> autoAlan)
4396   {
4397     AlignFrame af = null;
4398     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4399             uniqueSeqSetId, viewId);
4400
4401     af.setFileName(file, FileFormat.Jalview);
4402
4403     for (int i = 0; i < JSEQ.length; i++)
4404     {
4405       af.viewport.setSequenceColour(
4406               af.viewport.getAlignment().getSequenceAt(i),
4407               new java.awt.Color(JSEQ[i].getColour()));
4408     }
4409
4410     if (al.hasSeqrep())
4411     {
4412       af.getViewport().setColourByReferenceSeq(true);
4413       af.getViewport().setDisplayReferenceSeq(true);
4414     }
4415
4416     af.viewport.setGatherViewsHere(view.getGatheredViews());
4417
4418     if (view.getSequenceSetId() != null)
4419     {
4420       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4421
4422       af.viewport.setSequenceSetId(uniqueSeqSetId);
4423       if (av != null)
4424       {
4425         // propagate shared settings to this new view
4426         af.viewport.setHistoryList(av.getHistoryList());
4427         af.viewport.setRedoList(av.getRedoList());
4428       }
4429       else
4430       {
4431         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4432       }
4433       // TODO: check if this method can be called repeatedly without
4434       // side-effects if alignpanel already registered.
4435       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4436     }
4437     // apply Hidden regions to view.
4438     if (hiddenSeqs != null)
4439     {
4440       for (int s = 0; s < JSEQ.length; s++)
4441       {
4442         SequenceGroup hidden = new SequenceGroup();
4443         boolean isRepresentative = false;
4444         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4445         {
4446           isRepresentative = true;
4447           SequenceI sequenceToHide = al
4448                   .getSequenceAt(JSEQ[s].getHiddenSequences(r));
4449           hidden.addSequence(sequenceToHide, false);
4450           // remove from hiddenSeqs list so we don't try to hide it twice
4451           hiddenSeqs.remove(sequenceToHide);
4452         }
4453         if (isRepresentative)
4454         {
4455           SequenceI representativeSequence = al.getSequenceAt(s);
4456           hidden.addSequence(representativeSequence, false);
4457           af.viewport.hideRepSequences(representativeSequence, hidden);
4458         }
4459       }
4460
4461       SequenceI[] hseqs = hiddenSeqs
4462               .toArray(new SequenceI[hiddenSeqs.size()]);
4463       af.viewport.hideSequence(hseqs);
4464
4465     }
4466     // recover view properties and display parameters
4467
4468     af.viewport.setShowAnnotation(view.getShowAnnotation());
4469     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4470     af.viewport.setThreshold(view.getPidThreshold());
4471
4472     af.viewport.setColourText(view.getShowColourText());
4473
4474     af.viewport.setConservationSelected(view.getConservationSelected());
4475     af.viewport.setIncrement(view.getConsThreshold());
4476     af.viewport.setShowJVSuffix(view.getShowFullId());
4477     af.viewport.setRightAlignIds(view.getRightAlignIds());
4478     af.viewport.setFont(new java.awt.Font(view.getFontName(),
4479             view.getFontStyle(), view.getFontSize()), true);
4480     ViewStyleI vs = af.viewport.getViewStyle();
4481     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4482     af.viewport.setViewStyle(vs);
4483     // TODO: allow custom charWidth/Heights to be restored by updating them
4484     // after setting font - which means set above to false
4485     af.viewport.setRenderGaps(view.getRenderGaps());
4486     af.viewport.setWrapAlignment(view.getWrapAlignment());
4487     af.viewport.setShowAnnotation(view.getShowAnnotation());
4488
4489     af.viewport.setShowBoxes(view.getShowBoxes());
4490
4491     af.viewport.setShowText(view.getShowText());
4492
4493     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4494     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4495     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4496     af.viewport.setShowUnconserved(
4497             view.hasShowUnconserved() ? view.isShowUnconserved() : false);
4498     af.viewport.getRanges().setStartRes(view.getStartRes());
4499
4500     if (view.getViewName() != null)
4501     {
4502       af.viewport.viewName = view.getViewName();
4503       af.setInitialTabVisible();
4504     }
4505     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4506             view.getHeight());
4507     // startSeq set in af.alignPanel.updateLayout below
4508     af.alignPanel.updateLayout();
4509     ColourSchemeI cs = null;
4510     // apply colourschemes
4511     if (view.getBgColour() != null)
4512     {
4513       if (view.getBgColour().startsWith("ucs"))
4514       {
4515         cs = getUserColourScheme(jms, view.getBgColour());
4516       }
4517       else if (view.getBgColour().startsWith("Annotation"))
4518       {
4519         AnnotationColours viewAnnColour = view.getAnnotationColours();
4520         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4521
4522         // annpos
4523
4524       }
4525       else
4526       {
4527         cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour());
4528       }
4529     }
4530
4531     af.viewport.setGlobalColourScheme(cs);
4532     af.viewport.getResidueShading().setThreshold(view.getPidThreshold(),
4533             view.getIgnoreGapsinConsensus());
4534     af.viewport.getResidueShading()
4535             .setConsensus(af.viewport.getSequenceConsensusHash());
4536     af.viewport.setColourAppliesToAllGroups(false);
4537
4538     if (view.getConservationSelected() && cs != null)
4539     {
4540       af.viewport.getResidueShading()
4541               .setConservationInc(view.getConsThreshold());
4542     }
4543
4544     af.changeColour(cs);
4545
4546     af.viewport.setColourAppliesToAllGroups(true);
4547
4548     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4549
4550     if (view.hasCentreColumnLabels())
4551     {
4552       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4553     }
4554     if (view.hasIgnoreGapsinConsensus())
4555     {
4556       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4557               null);
4558     }
4559     if (view.hasFollowHighlight())
4560     {
4561       af.viewport.setFollowHighlight(view.getFollowHighlight());
4562     }
4563     if (view.hasFollowSelection())
4564     {
4565       af.viewport.followSelection = view.getFollowSelection();
4566     }
4567     if (view.hasShowConsensusHistogram())
4568     {
4569       af.viewport
4570               .setShowConsensusHistogram(view.getShowConsensusHistogram());
4571     }
4572     else
4573     {
4574       af.viewport.setShowConsensusHistogram(true);
4575     }
4576     if (view.hasShowSequenceLogo())
4577     {
4578       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4579     }
4580     else
4581     {
4582       af.viewport.setShowSequenceLogo(false);
4583     }
4584     if (view.hasNormaliseSequenceLogo())
4585     {
4586       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4587     }
4588     if (view.hasShowDbRefTooltip())
4589     {
4590       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4591     }
4592     if (view.hasShowNPfeatureTooltip())
4593     {
4594       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4595     }
4596     if (view.hasShowGroupConsensus())
4597     {
4598       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4599     }
4600     else
4601     {
4602       af.viewport.setShowGroupConsensus(false);
4603     }
4604     if (view.hasShowGroupConservation())
4605     {
4606       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4607     }
4608     else
4609     {
4610       af.viewport.setShowGroupConservation(false);
4611     }
4612
4613     // recover featre settings
4614     if (jms.getFeatureSettings() != null)
4615     {
4616       FeaturesDisplayed fdi;
4617       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4618       String[] renderOrder = new String[jms.getFeatureSettings()
4619               .getSettingCount()];
4620       Map<String, FeatureColourI> featureColours = new Hashtable<>();
4621       Map<String, Float> featureOrder = new Hashtable<>();
4622
4623       for (int fs = 0; fs < jms.getFeatureSettings()
4624               .getSettingCount(); fs++)
4625       {
4626         Setting setting = jms.getFeatureSettings().getSetting(fs);
4627         if (setting.hasMincolour())
4628         {
4629           FeatureColourI gc = setting.hasMin()
4630                   ? new FeatureColour(new Color(setting.getMincolour()),
4631                           new Color(setting.getColour()), setting.getMin(),
4632                           setting.getMax())
4633                   : new FeatureColour(new Color(setting.getMincolour()),
4634                           new Color(setting.getColour()), 0, 1);
4635           if (setting.hasThreshold())
4636           {
4637             gc.setThreshold(setting.getThreshold());
4638             int threshstate = setting.getThreshstate();
4639             // -1 = None, 0 = Below, 1 = Above threshold
4640             if (threshstate == 0)
4641             {
4642               gc.setBelowThreshold(true);
4643             }
4644             else if (threshstate == 1)
4645             {
4646               gc.setAboveThreshold(true);
4647             }
4648           }
4649           gc.setAutoScaled(true); // default
4650           if (setting.hasAutoScale())
4651           {
4652             gc.setAutoScaled(setting.getAutoScale());
4653           }
4654           if (setting.hasColourByLabel())
4655           {
4656             gc.setColourByLabel(setting.getColourByLabel());
4657           }
4658           // and put in the feature colour table.
4659           featureColours.put(setting.getType(), gc);
4660         }
4661         else
4662         {
4663           featureColours.put(setting.getType(),
4664                   new FeatureColour(new Color(setting.getColour())));
4665         }
4666         renderOrder[fs] = setting.getType();
4667         if (setting.hasOrder())
4668         {
4669           featureOrder.put(setting.getType(), setting.getOrder());
4670         }
4671         else
4672         {
4673           featureOrder.put(setting.getType(), new Float(
4674                   fs / jms.getFeatureSettings().getSettingCount()));
4675         }
4676         if (setting.getDisplay())
4677         {
4678           fdi.setVisible(setting.getType());
4679         }
4680       }
4681       Map<String, Boolean> fgtable = new Hashtable<>();
4682       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4683       {
4684         Group grp = jms.getFeatureSettings().getGroup(gs);
4685         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4686       }
4687       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4688       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4689       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4690       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4691               fgtable, featureColours, 1.0f, featureOrder);
4692       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4693               .transferSettings(frs);
4694
4695     }
4696
4697     if (view.getHiddenColumnsCount() > 0)
4698     {
4699       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4700       {
4701         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(),
4702                 view.getHiddenColumns(c).getEnd() // +1
4703         );
4704       }
4705     }
4706     if (view.getCalcIdParam() != null)
4707     {
4708       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4709       {
4710         if (calcIdParam != null)
4711         {
4712           if (recoverCalcIdParam(calcIdParam, af.viewport))
4713           {
4714           }
4715           else
4716           {
4717             warn("Couldn't recover parameters for "
4718                     + calcIdParam.getCalcId());
4719           }
4720         }
4721       }
4722     }
4723     af.setMenusFromViewport(af.viewport);
4724     af.setTitle(view.getTitle());
4725     // TODO: we don't need to do this if the viewport is aready visible.
4726     /*
4727      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4728      * has a 'cdna/protein complement' view, in which case save it in order to
4729      * populate a SplitFrame once all views have been read in.
4730      */
4731     String complementaryViewId = view.getComplementId();
4732     if (complementaryViewId == null)
4733     {
4734       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4735               view.getHeight());
4736       // recompute any autoannotation
4737       af.alignPanel.updateAnnotation(false, true);
4738       reorderAutoannotation(af, al, autoAlan);
4739       af.alignPanel.alignmentChanged();
4740     }
4741     else
4742     {
4743       splitFrameCandidates.put(view, af);
4744     }
4745     return af;
4746   }
4747
4748   /**
4749    * Reads saved data to restore Colour by Annotation settings
4750    * 
4751    * @param viewAnnColour
4752    * @param af
4753    * @param al
4754    * @param jms
4755    * @param checkGroupAnnColour
4756    * @return
4757    */
4758   private ColourSchemeI constructAnnotationColour(
4759           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
4760           JalviewModelSequence jms, boolean checkGroupAnnColour)
4761   {
4762     boolean propagateAnnColour = false;
4763     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4764     if (checkGroupAnnColour && al.getGroups() != null
4765             && al.getGroups().size() > 0)
4766     {
4767       // pre 2.8.1 behaviour
4768       // check to see if we should transfer annotation colours
4769       propagateAnnColour = true;
4770       for (SequenceGroup sg : al.getGroups())
4771       {
4772         if (sg.getColourScheme() instanceof AnnotationColourGradient)
4773         {
4774           propagateAnnColour = false;
4775         }
4776       }
4777     }
4778
4779     /*
4780      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
4781      */
4782     String annotationId = viewAnnColour.getAnnotation();
4783     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
4784
4785     /*
4786      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
4787      */
4788     if (matchedAnnotation == null
4789             && annAlignment.getAlignmentAnnotation() != null)
4790     {
4791       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4792       {
4793         if (annotationId
4794                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
4795         {
4796           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
4797           break;
4798         }
4799       }
4800     }
4801     if (matchedAnnotation == null)
4802     {
4803       System.err.println("Failed to match annotation colour scheme for "
4804               + annotationId);
4805       return null;
4806     }
4807     if (matchedAnnotation.getThreshold() == null)
4808     {
4809       matchedAnnotation.setThreshold(new GraphLine(
4810               viewAnnColour.getThreshold(), "Threshold", Color.black));
4811     }
4812
4813     AnnotationColourGradient cs = null;
4814     if (viewAnnColour.getColourScheme().equals("None"))
4815     {
4816       cs = new AnnotationColourGradient(matchedAnnotation,
4817               new Color(viewAnnColour.getMinColour()),
4818               new Color(viewAnnColour.getMaxColour()),
4819               viewAnnColour.getAboveThreshold());
4820     }
4821     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4822     {
4823       cs = new AnnotationColourGradient(matchedAnnotation,
4824               getUserColourScheme(jms, viewAnnColour.getColourScheme()),
4825               viewAnnColour.getAboveThreshold());
4826     }
4827     else
4828     {
4829       cs = new AnnotationColourGradient(matchedAnnotation,
4830               ColourSchemeProperty.getColourScheme(al,
4831                       viewAnnColour.getColourScheme()),
4832               viewAnnColour.getAboveThreshold());
4833     }
4834
4835     boolean perSequenceOnly = viewAnnColour.isPerSequence();
4836     boolean useOriginalColours = viewAnnColour.isPredefinedColours();
4837     cs.setSeqAssociated(perSequenceOnly);
4838     cs.setPredefinedColours(useOriginalColours);
4839
4840     if (propagateAnnColour && al.getGroups() != null)
4841     {
4842       // Also use these settings for all the groups
4843       for (int g = 0; g < al.getGroups().size(); g++)
4844       {
4845         SequenceGroup sg = al.getGroups().get(g);
4846         if (sg.getGroupColourScheme() == null)
4847         {
4848           continue;
4849         }
4850
4851         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
4852                 matchedAnnotation, sg.getColourScheme(),
4853                 viewAnnColour.getAboveThreshold());
4854         sg.setColourScheme(groupScheme);
4855         groupScheme.setSeqAssociated(perSequenceOnly);
4856         groupScheme.setPredefinedColours(useOriginalColours);
4857       }
4858     }
4859     return cs;
4860   }
4861
4862   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
4863           List<JvAnnotRow> autoAlan)
4864   {
4865     // copy over visualization settings for autocalculated annotation in the
4866     // view
4867     if (al.getAlignmentAnnotation() != null)
4868     {
4869       /**
4870        * Kludge for magic autoannotation names (see JAL-811)
4871        */
4872       String[] magicNames = new String[] { "Consensus", "Quality",
4873           "Conservation" };
4874       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4875       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
4876       for (String nm : magicNames)
4877       {
4878         visan.put(nm, nullAnnot);
4879       }
4880       for (JvAnnotRow auan : autoAlan)
4881       {
4882         visan.put(auan.template.label
4883                 + (auan.template.getCalcId() == null ? ""
4884                         : "\t" + auan.template.getCalcId()),
4885                 auan);
4886       }
4887       int hSize = al.getAlignmentAnnotation().length;
4888       List<JvAnnotRow> reorder = new ArrayList<>();
4889       // work through any autoCalculated annotation already on the view
4890       // removing it if it should be placed in a different location on the
4891       // annotation panel.
4892       List<String> remains = new ArrayList<>(visan.keySet());
4893       for (int h = 0; h < hSize; h++)
4894       {
4895         jalview.datamodel.AlignmentAnnotation jalan = al
4896                 .getAlignmentAnnotation()[h];
4897         if (jalan.autoCalculated)
4898         {
4899           String k;
4900           JvAnnotRow valan = visan.get(k = jalan.label);
4901           if (jalan.getCalcId() != null)
4902           {
4903             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4904           }
4905
4906           if (valan != null)
4907           {
4908             // delete the auto calculated row from the alignment
4909             al.deleteAnnotation(jalan, false);
4910             remains.remove(k);
4911             hSize--;
4912             h--;
4913             if (valan != nullAnnot)
4914             {
4915               if (jalan != valan.template)
4916               {
4917                 // newly created autoannotation row instance
4918                 // so keep a reference to the visible annotation row
4919                 // and copy over all relevant attributes
4920                 if (valan.template.graphHeight >= 0)
4921
4922                 {
4923                   jalan.graphHeight = valan.template.graphHeight;
4924                 }
4925                 jalan.visible = valan.template.visible;
4926               }
4927               reorder.add(new JvAnnotRow(valan.order, jalan));
4928             }
4929           }
4930         }
4931       }
4932       // Add any (possibly stale) autocalculated rows that were not appended to
4933       // the view during construction
4934       for (String other : remains)
4935       {
4936         JvAnnotRow othera = visan.get(other);
4937         if (othera != nullAnnot && othera.template.getCalcId() != null
4938                 && othera.template.getCalcId().length() > 0)
4939         {
4940           reorder.add(othera);
4941         }
4942       }
4943       // now put the automatic annotation in its correct place
4944       int s = 0, srt[] = new int[reorder.size()];
4945       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4946       for (JvAnnotRow jvar : reorder)
4947       {
4948         rws[s] = jvar;
4949         srt[s++] = jvar.order;
4950       }
4951       reorder.clear();
4952       jalview.util.QuickSort.sort(srt, rws);
4953       // and re-insert the annotation at its correct position
4954       for (JvAnnotRow jvar : rws)
4955       {
4956         al.addAnnotation(jvar.template, jvar.order);
4957       }
4958       af.alignPanel.adjustAnnotationHeight();
4959     }
4960   }
4961
4962   Hashtable skipList = null;
4963
4964   /**
4965    * TODO remove this method
4966    * 
4967    * @param view
4968    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4969    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4970    *         throw new Error("Implementation Error. No skipList defined for this
4971    *         Jalview2XML instance."); } return (AlignFrame)
4972    *         skipList.get(view.getSequenceSetId()); }
4973    */
4974
4975   /**
4976    * Check if the Jalview view contained in object should be skipped or not.
4977    * 
4978    * @param object
4979    * @return true if view's sequenceSetId is a key in skipList
4980    */
4981   private boolean skipViewport(JalviewModel object)
4982   {
4983     if (skipList == null)
4984     {
4985       return false;
4986     }
4987     String id;
4988     if (skipList.containsKey(
4989             id = object.getJalviewModelSequence().getViewport()[0]
4990                     .getSequenceSetId()))
4991     {
4992       if (Cache.log != null && Cache.log.isDebugEnabled())
4993       {
4994         Cache.log.debug("Skipping seuqence set id " + id);
4995       }
4996       return true;
4997     }
4998     return false;
4999   }
5000
5001   public void addToSkipList(AlignFrame af)
5002   {
5003     if (skipList == null)
5004     {
5005       skipList = new Hashtable();
5006     }
5007     skipList.put(af.getViewport().getSequenceSetId(), af);
5008   }
5009
5010   public void clearSkipList()
5011   {
5012     if (skipList != null)
5013     {
5014       skipList.clear();
5015       skipList = null;
5016     }
5017   }
5018
5019   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5020           boolean ignoreUnrefed)
5021   {
5022     jalview.datamodel.AlignmentI ds = getDatasetFor(
5023             vamsasSet.getDatasetId());
5024     Vector dseqs = null;
5025     if (ds == null)
5026     {
5027       // create a list of new dataset sequences
5028       dseqs = new Vector();
5029     }
5030     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
5031     {
5032       Sequence vamsasSeq = vamsasSet.getSequence(i);
5033       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5034     }
5035     // create a new dataset
5036     if (ds == null)
5037     {
5038       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5039       dseqs.copyInto(dsseqs);
5040       ds = new jalview.datamodel.Alignment(dsseqs);
5041       debug("Created new dataset " + vamsasSet.getDatasetId()
5042               + " for alignment " + System.identityHashCode(al));
5043       addDatasetRef(vamsasSet.getDatasetId(), ds);
5044     }
5045     // set the dataset for the newly imported alignment.
5046     if (al.getDataset() == null && !ignoreUnrefed)
5047     {
5048       al.setDataset(ds);
5049     }
5050   }
5051
5052   /**
5053    * 
5054    * @param vamsasSeq
5055    *          sequence definition to create/merge dataset sequence for
5056    * @param ds
5057    *          dataset alignment
5058    * @param dseqs
5059    *          vector to add new dataset sequence to
5060    * @param ignoreUnrefed
5061    *          - when true, don't create new sequences from vamsasSeq if it's id
5062    *          doesn't already have an asssociated Jalview sequence.
5063    * @param vseqpos
5064    *          - used to reorder the sequence in the alignment according to the
5065    *          vamsasSeq array ordering, to preserve ordering of dataset
5066    */
5067   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5068           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5069   {
5070     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5071     // xRef Codon Maps
5072     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5073     boolean reorder = false;
5074     SequenceI dsq = null;
5075     if (sq != null && sq.getDatasetSequence() != null)
5076     {
5077       dsq = sq.getDatasetSequence();
5078     }
5079     else
5080     {
5081       reorder = true;
5082     }
5083     if (sq == null && ignoreUnrefed)
5084     {
5085       return;
5086     }
5087     String sqid = vamsasSeq.getDsseqid();
5088     if (dsq == null)
5089     {
5090       // need to create or add a new dataset sequence reference to this sequence
5091       if (sqid != null)
5092       {
5093         dsq = seqRefIds.get(sqid);
5094       }
5095       // check again
5096       if (dsq == null)
5097       {
5098         // make a new dataset sequence
5099         dsq = sq.createDatasetSequence();
5100         if (sqid == null)
5101         {
5102           // make up a new dataset reference for this sequence
5103           sqid = seqHash(dsq);
5104         }
5105         dsq.setVamsasId(uniqueSetSuffix + sqid);
5106         seqRefIds.put(sqid, dsq);
5107         if (ds == null)
5108         {
5109           if (dseqs != null)
5110           {
5111             dseqs.addElement(dsq);
5112           }
5113         }
5114         else
5115         {
5116           ds.addSequence(dsq);
5117         }
5118       }
5119       else
5120       {
5121         if (sq != dsq)
5122         { // make this dataset sequence sq's dataset sequence
5123           sq.setDatasetSequence(dsq);
5124           // and update the current dataset alignment
5125           if (ds == null)
5126           {
5127             if (dseqs != null)
5128             {
5129               if (!dseqs.contains(dsq))
5130               {
5131                 dseqs.add(dsq);
5132               }
5133             }
5134             else
5135             {
5136               if (ds.findIndex(dsq) < 0)
5137               {
5138                 ds.addSequence(dsq);
5139               }
5140             }
5141           }
5142         }
5143       }
5144     }
5145     // TODO: refactor this as a merge dataset sequence function
5146     // now check that sq (the dataset sequence) sequence really is the union of
5147     // all references to it
5148     // boolean pre = sq.getStart() < dsq.getStart();
5149     // boolean post = sq.getEnd() > dsq.getEnd();
5150     // if (pre || post)
5151     if (sq != dsq)
5152     {
5153       // StringBuffer sb = new StringBuffer();
5154       String newres = jalview.analysis.AlignSeq.extractGaps(
5155               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5156       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5157               && newres.length() > dsq.getLength())
5158       {
5159         // Update with the longer sequence.
5160         synchronized (dsq)
5161         {
5162           /*
5163            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5164            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5165            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5166            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5167            */
5168           dsq.setSequence(newres);
5169         }
5170         // TODO: merges will never happen if we 'know' we have the real dataset
5171         // sequence - this should be detected when id==dssid
5172         System.err.println(
5173                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5174         // + (pre ? "prepended" : "") + " "
5175         // + (post ? "appended" : ""));
5176       }
5177     }
5178     else
5179     {
5180       // sequence refs are identical. We may need to update the existing dataset
5181       // alignment with this one, though.
5182       if (ds != null && dseqs == null)
5183       {
5184         int opos = ds.findIndex(dsq);
5185         SequenceI tseq = null;
5186         if (opos != -1 && vseqpos != opos)
5187         {
5188           // remove from old position
5189           ds.deleteSequence(dsq);
5190         }
5191         if (vseqpos < ds.getHeight())
5192         {
5193           if (vseqpos != opos)
5194           {
5195             // save sequence at destination position
5196             tseq = ds.getSequenceAt(vseqpos);
5197             ds.replaceSequenceAt(vseqpos, dsq);
5198             ds.addSequence(tseq);
5199           }
5200         }
5201         else
5202         {
5203           ds.addSequence(dsq);
5204         }
5205       }
5206     }
5207   }
5208
5209   /*
5210    * TODO use AlignmentI here and in related methods - needs
5211    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5212    */
5213   Hashtable<String, AlignmentI> datasetIds = null;
5214
5215   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5216
5217   private AlignmentI getDatasetFor(String datasetId)
5218   {
5219     if (datasetIds == null)
5220     {
5221       datasetIds = new Hashtable<>();
5222       return null;
5223     }
5224     if (datasetIds.containsKey(datasetId))
5225     {
5226       return datasetIds.get(datasetId);
5227     }
5228     return null;
5229   }
5230
5231   private void addDatasetRef(String datasetId, AlignmentI dataset)
5232   {
5233     if (datasetIds == null)
5234     {
5235       datasetIds = new Hashtable<>();
5236     }
5237     datasetIds.put(datasetId, dataset);
5238   }
5239
5240   /**
5241    * make a new dataset ID for this jalview dataset alignment
5242    * 
5243    * @param dataset
5244    * @return
5245    */
5246   private String getDatasetIdRef(AlignmentI dataset)
5247   {
5248     if (dataset.getDataset() != null)
5249     {
5250       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5251     }
5252     String datasetId = makeHashCode(dataset, null);
5253     if (datasetId == null)
5254     {
5255       // make a new datasetId and record it
5256       if (dataset2Ids == null)
5257       {
5258         dataset2Ids = new IdentityHashMap<>();
5259       }
5260       else
5261       {
5262         datasetId = dataset2Ids.get(dataset);
5263       }
5264       if (datasetId == null)
5265       {
5266         datasetId = "ds" + dataset2Ids.size() + 1;
5267         dataset2Ids.put(dataset, datasetId);
5268       }
5269     }
5270     return datasetId;
5271   }
5272
5273   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5274   {
5275     for (int d = 0; d < sequence.getDBRefCount(); d++)
5276     {
5277       DBRef dr = sequence.getDBRef(d);
5278       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5279               sequence.getDBRef(d).getSource(),
5280               sequence.getDBRef(d).getVersion(),
5281               sequence.getDBRef(d).getAccessionId());
5282       if (dr.getMapping() != null)
5283       {
5284         entry.setMap(addMapping(dr.getMapping()));
5285       }
5286       datasetSequence.addDBRef(entry);
5287     }
5288   }
5289
5290   private jalview.datamodel.Mapping addMapping(Mapping m)
5291   {
5292     SequenceI dsto = null;
5293     // Mapping m = dr.getMapping();
5294     int fr[] = new int[m.getMapListFromCount() * 2];
5295     Enumeration f = m.enumerateMapListFrom();
5296     for (int _i = 0; f.hasMoreElements(); _i += 2)
5297     {
5298       MapListFrom mf = (MapListFrom) f.nextElement();
5299       fr[_i] = mf.getStart();
5300       fr[_i + 1] = mf.getEnd();
5301     }
5302     int fto[] = new int[m.getMapListToCount() * 2];
5303     f = m.enumerateMapListTo();
5304     for (int _i = 0; f.hasMoreElements(); _i += 2)
5305     {
5306       MapListTo mf = (MapListTo) f.nextElement();
5307       fto[_i] = mf.getStart();
5308       fto[_i + 1] = mf.getEnd();
5309     }
5310     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5311             fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
5312     if (m.getMappingChoice() != null)
5313     {
5314       MappingChoice mc = m.getMappingChoice();
5315       if (mc.getDseqFor() != null)
5316       {
5317         String dsfor = "" + mc.getDseqFor();
5318         if (seqRefIds.containsKey(dsfor))
5319         {
5320           /**
5321            * recover from hash
5322            */
5323           jmap.setTo(seqRefIds.get(dsfor));
5324         }
5325         else
5326         {
5327           frefedSequence.add(newMappingRef(dsfor, jmap));
5328         }
5329       }
5330       else
5331       {
5332         /**
5333          * local sequence definition
5334          */
5335         Sequence ms = mc.getSequence();
5336         SequenceI djs = null;
5337         String sqid = ms.getDsseqid();
5338         if (sqid != null && sqid.length() > 0)
5339         {
5340           /*
5341            * recover dataset sequence
5342            */
5343           djs = seqRefIds.get(sqid);
5344         }
5345         else
5346         {
5347           System.err.println(
5348                   "Warning - making up dataset sequence id for DbRef sequence map reference");
5349           sqid = ((Object) ms).toString(); // make up a new hascode for
5350           // undefined dataset sequence hash
5351           // (unlikely to happen)
5352         }
5353
5354         if (djs == null)
5355         {
5356           /**
5357            * make a new dataset sequence and add it to refIds hash
5358            */
5359           djs = new jalview.datamodel.Sequence(ms.getName(),
5360                   ms.getSequence());
5361           djs.setStart(jmap.getMap().getToLowest());
5362           djs.setEnd(jmap.getMap().getToHighest());
5363           djs.setVamsasId(uniqueSetSuffix + sqid);
5364           jmap.setTo(djs);
5365           incompleteSeqs.put(sqid, djs);
5366           seqRefIds.put(sqid, djs);
5367
5368         }
5369         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5370         addDBRefs(djs, ms);
5371
5372       }
5373     }
5374     return (jmap);
5375
5376   }
5377
5378   /**
5379    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
5380    * view as XML (but not to file), and then reloading it
5381    * 
5382    * @param ap
5383    * @return
5384    */
5385   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
5386   {
5387     initSeqRefs();
5388     JalviewModel jm = saveState(ap, null, null, null);
5389
5390     uniqueSetSuffix = "";
5391     jm.getJalviewModelSequence().getViewport(0).setId(null);
5392     // we don't overwrite the view we just copied
5393
5394     if (this.frefedSequence == null)
5395     {
5396       frefedSequence = new Vector<>();
5397     }
5398
5399     viewportsAdded.clear();
5400
5401     AlignFrame af = loadFromObject(jm, null, false, null);
5402     af.alignPanels.clear();
5403     af.closeMenuItem_actionPerformed(true);
5404
5405     /*
5406      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5407      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5408      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5409      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5410      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5411      */
5412
5413     return af.alignPanel;
5414   }
5415
5416   private Hashtable jvids2vobj;
5417
5418   private void warn(String msg)
5419   {
5420     warn(msg, null);
5421   }
5422
5423   private void warn(String msg, Exception e)
5424   {
5425     if (Cache.log != null)
5426     {
5427       if (e != null)
5428       {
5429         Cache.log.warn(msg, e);
5430       }
5431       else
5432       {
5433         Cache.log.warn(msg);
5434       }
5435     }
5436     else
5437     {
5438       System.err.println("Warning: " + msg);
5439       if (e != null)
5440       {
5441         e.printStackTrace();
5442       }
5443     }
5444   }
5445
5446   private void debug(String string)
5447   {
5448     debug(string, null);
5449   }
5450
5451   private void debug(String msg, Exception e)
5452   {
5453     if (Cache.log != null)
5454     {
5455       if (e != null)
5456       {
5457         Cache.log.debug(msg, e);
5458       }
5459       else
5460       {
5461         Cache.log.debug(msg);
5462       }
5463     }
5464     else
5465     {
5466       System.err.println("Warning: " + msg);
5467       if (e != null)
5468       {
5469         e.printStackTrace();
5470       }
5471     }
5472   }
5473
5474   /**
5475    * set the object to ID mapping tables used to write/recover objects and XML
5476    * ID strings for the jalview project. If external tables are provided then
5477    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5478    * object goes out of scope. - also populates the datasetIds hashtable with
5479    * alignment objects containing dataset sequences
5480    * 
5481    * @param vobj2jv
5482    *          Map from ID strings to jalview datamodel
5483    * @param jv2vobj
5484    *          Map from jalview datamodel to ID strings
5485    * 
5486    * 
5487    */
5488   public void setObjectMappingTables(Hashtable vobj2jv,
5489           IdentityHashMap jv2vobj)
5490   {
5491     this.jv2vobj = jv2vobj;
5492     this.vobj2jv = vobj2jv;
5493     Iterator ds = jv2vobj.keySet().iterator();
5494     String id;
5495     while (ds.hasNext())
5496     {
5497       Object jvobj = ds.next();
5498       id = jv2vobj.get(jvobj).toString();
5499       if (jvobj instanceof jalview.datamodel.Alignment)
5500       {
5501         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5502         {
5503           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5504         }
5505       }
5506       else if (jvobj instanceof jalview.datamodel.Sequence)
5507       {
5508         // register sequence object so the XML parser can recover it.
5509         if (seqRefIds == null)
5510         {
5511           seqRefIds = new HashMap<>();
5512         }
5513         if (seqsToIds == null)
5514         {
5515           seqsToIds = new IdentityHashMap<>();
5516         }
5517         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5518         seqsToIds.put((SequenceI) jvobj, id);
5519       }
5520       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5521       {
5522         String anid;
5523         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5524         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5525         if (jvann.annotationId == null)
5526         {
5527           jvann.annotationId = anid;
5528         }
5529         if (!jvann.annotationId.equals(anid))
5530         {
5531           // TODO verify that this is the correct behaviour
5532           this.warn("Overriding Annotation ID for " + anid
5533                   + " from different id : " + jvann.annotationId);
5534           jvann.annotationId = anid;
5535         }
5536       }
5537       else if (jvobj instanceof String)
5538       {
5539         if (jvids2vobj == null)
5540         {
5541           jvids2vobj = new Hashtable();
5542           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5543         }
5544       }
5545       else
5546       {
5547         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5548       }
5549     }
5550   }
5551
5552   /**
5553    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5554    * objects created from the project archive. If string is null (default for
5555    * construction) then suffix will be set automatically.
5556    * 
5557    * @param string
5558    */
5559   public void setUniqueSetSuffix(String string)
5560   {
5561     uniqueSetSuffix = string;
5562
5563   }
5564
5565   /**
5566    * uses skipList2 as the skipList for skipping views on sequence sets
5567    * associated with keys in the skipList
5568    * 
5569    * @param skipList2
5570    */
5571   public void setSkipList(Hashtable skipList2)
5572   {
5573     skipList = skipList2;
5574   }
5575
5576   /**
5577    * Reads the jar entry of given name and returns its contents, or null if the
5578    * entry is not found.
5579    * 
5580    * @param jprovider
5581    * @param jarEntryName
5582    * @return
5583    */
5584   protected String readJarEntry(jarInputStreamProvider jprovider,
5585           String jarEntryName)
5586   {
5587     String result = null;
5588     BufferedReader in = null;
5589
5590     try
5591     {
5592       /*
5593        * Reopen the jar input stream and traverse its entries to find a matching
5594        * name
5595        */
5596       JarInputStream jin = jprovider.getJarInputStream();
5597       JarEntry entry = null;
5598       do
5599       {
5600         entry = jin.getNextJarEntry();
5601       } while (entry != null && !entry.getName().equals(jarEntryName));
5602
5603       if (entry != null)
5604       {
5605         StringBuilder out = new StringBuilder(256);
5606         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5607         String data;
5608
5609         while ((data = in.readLine()) != null)
5610         {
5611           out.append(data);
5612         }
5613         result = out.toString();
5614       }
5615       else
5616       {
5617         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5618       }
5619     } catch (Exception ex)
5620     {
5621       ex.printStackTrace();
5622     } finally
5623     {
5624       if (in != null)
5625       {
5626         try
5627         {
5628           in.close();
5629         } catch (IOException e)
5630         {
5631           // ignore
5632         }
5633       }
5634     }
5635
5636     return result;
5637   }
5638
5639   /**
5640    * Returns an incrementing counter (0, 1, 2...)
5641    * 
5642    * @return
5643    */
5644   private synchronized int nextCounter()
5645   {
5646     return counter++;
5647   }
5648 }