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