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