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