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