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