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