JAL-3187 save and restore flags for show complement features
[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 origFile
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 origFile)
3173   {
3174     BufferedReader in = null;
3175     PrintWriter out = null;
3176     String suffix = ".tmp";
3177     if (origFile == null)
3178     {
3179       origFile = jarEntryName;
3180     }
3181     int sfpos = origFile.lastIndexOf(".");
3182     if (sfpos > -1 && sfpos < (origFile.length() - 3))
3183     {
3184       suffix = "." + origFile.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                     "Warning JAL-2154 regression: updating start/end for sequence "
3340                             + tmpSeq.toString() + " to " + jseq);
3341           }
3342         }
3343         else
3344         {
3345           incompleteSeqs.remove(seqId);
3346         }
3347         if (vamsasSeqs.size() > vi
3348                 && vamsasSeqs.get(vi).getId().equals(seqId))
3349         {
3350           // most likely we are reading a dataset XML document so
3351           // update from vamsasSeq section of XML for this sequence
3352           tmpSeq.setName(vamsasSeqs.get(vi).getName());
3353           tmpSeq.setDescription(vamsasSeqs.get(vi).getDescription());
3354           tmpSeq.setSequence(vamsasSeqs.get(vi).getSequence());
3355           vi++;
3356         }
3357         else
3358         {
3359           // reading multiple views, so vamsasSeq set is a subset of JSeq
3360           multipleView = true;
3361         }
3362         tmpSeq.setStart(jseq.getStart());
3363         tmpSeq.setEnd(jseq.getEnd());
3364         tmpseqs.add(tmpSeq);
3365       }
3366       else
3367       {
3368         Sequence vamsasSeq = vamsasSeqs.get(vi);
3369         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq.getName(),
3370                 vamsasSeq.getSequence());
3371         tmpSeq.setDescription(vamsasSeq.getDescription());
3372         tmpSeq.setStart(jseq.getStart());
3373         tmpSeq.setEnd(jseq.getEnd());
3374         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
3375         seqRefIds.put(vamsasSeq.getId(), tmpSeq);
3376         tmpseqs.add(tmpSeq);
3377         vi++;
3378       }
3379
3380       if (safeBoolean(jseq.isViewreference()))
3381       {
3382         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
3383       }
3384
3385       if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
3386       {
3387         if (hiddenSeqs == null)
3388         {
3389           hiddenSeqs = new ArrayList<>();
3390         }
3391
3392         hiddenSeqs.add(tmpSeq);
3393       }
3394     }
3395
3396     // /
3397     // Create the alignment object from the sequence set
3398     // ///////////////////////////////
3399     SequenceI[] orderedSeqs = tmpseqs
3400             .toArray(new SequenceI[tmpseqs.size()]);
3401
3402     AlignmentI al = null;
3403     // so we must create or recover the dataset alignment before going further
3404     // ///////////////////////////////
3405     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
3406     {
3407       // older jalview projects do not have a dataset - so creat alignment and
3408       // dataset
3409       al = new Alignment(orderedSeqs);
3410       al.setDataset(null);
3411     }
3412     else
3413     {
3414       boolean isdsal = jalviewModel.getViewport().isEmpty();
3415       if (isdsal)
3416       {
3417         // we are importing a dataset record, so
3418         // recover reference to an alignment already materialsed as dataset
3419         al = getDatasetFor(vamsasSet.getDatasetId());
3420       }
3421       if (al == null)
3422       {
3423         // materialse the alignment
3424         al = new Alignment(orderedSeqs);
3425       }
3426       if (isdsal)
3427       {
3428         addDatasetRef(vamsasSet.getDatasetId(), al);
3429       }
3430
3431       // finally, verify all data in vamsasSet is actually present in al
3432       // passing on flag indicating if it is actually a stored dataset
3433       recoverDatasetFor(vamsasSet, al, isdsal, uniqueSeqSetId);
3434     }
3435
3436     if (referenceseqForView != null)
3437     {
3438       al.setSeqrep(referenceseqForView);
3439     }
3440     // / Add the alignment properties
3441     for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
3442     {
3443       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
3444               .get(i);
3445       al.setProperty(ssp.getKey(), ssp.getValue());
3446     }
3447
3448     // ///////////////////////////////
3449
3450     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
3451     if (!multipleView)
3452     {
3453       // load sequence features, database references and any associated PDB
3454       // structures for the alignment
3455       //
3456       // prior to 2.10, this part would only be executed the first time a
3457       // sequence was encountered, but not afterwards.
3458       // now, for 2.10 projects, this is also done if the xml doc includes
3459       // dataset sequences not actually present in any particular view.
3460       //
3461       for (int i = 0; i < vamsasSeqs.size(); i++)
3462       {
3463         JSeq jseq = jseqs.get(i);
3464         if (jseq.getFeatures().size() > 0)
3465         {
3466           List<Feature> features = jseq.getFeatures();
3467           for (int f = 0; f < features.size(); f++)
3468           {
3469             Feature feat = features.get(f);
3470             SequenceFeature sf = new SequenceFeature(feat.getType(),
3471                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
3472                     safeFloat(feat.getScore()), feat.getFeatureGroup());
3473             sf.setStatus(feat.getStatus());
3474
3475             /*
3476              * load any feature attributes - include map-valued attributes
3477              */
3478             Map<String, Map<String, String>> mapAttributes = new HashMap<>();
3479             for (int od = 0; od < feat.getOtherData().size(); od++)
3480             {
3481               OtherData keyValue = feat.getOtherData().get(od);
3482               String attributeName = keyValue.getKey();
3483               String attributeValue = keyValue.getValue();
3484               if (attributeName.startsWith("LINK"))
3485               {
3486                 sf.addLink(attributeValue);
3487               }
3488               else
3489               {
3490                 String subAttribute = keyValue.getKey2();
3491                 if (subAttribute == null)
3492                 {
3493                   // simple string-valued attribute
3494                   sf.setValue(attributeName, attributeValue);
3495                 }
3496                 else
3497                 {
3498                   // attribute 'key' has sub-attribute 'key2'
3499                   if (!mapAttributes.containsKey(attributeName))
3500                   {
3501                     mapAttributes.put(attributeName, new HashMap<>());
3502                   }
3503                   mapAttributes.get(attributeName).put(subAttribute,
3504                           attributeValue);
3505                 }
3506               }
3507             }
3508             for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
3509                     .entrySet())
3510             {
3511               sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
3512             }
3513
3514             // adds feature to datasequence's feature set (since Jalview 2.10)
3515             al.getSequenceAt(i).addSequenceFeature(sf);
3516           }
3517         }
3518         if (vamsasSeqs.get(i).getDBRef().size() > 0)
3519         {
3520           // adds dbrefs to datasequence's set (since Jalview 2.10)
3521           addDBRefs(
3522                   al.getSequenceAt(i).getDatasetSequence() == null
3523                           ? al.getSequenceAt(i)
3524                           : al.getSequenceAt(i).getDatasetSequence(),
3525                   vamsasSeqs.get(i));
3526         }
3527         if (jseq.getPdbids().size() > 0)
3528         {
3529           List<Pdbids> ids = jseq.getPdbids();
3530           for (int p = 0; p < ids.size(); p++)
3531           {
3532             Pdbids pdbid = ids.get(p);
3533             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
3534             entry.setId(pdbid.getId());
3535             if (pdbid.getType() != null)
3536             {
3537               if (PDBEntry.Type.getType(pdbid.getType()) != null)
3538               {
3539                 entry.setType(PDBEntry.Type.getType(pdbid.getType()));
3540               }
3541               else
3542               {
3543                 entry.setType(PDBEntry.Type.FILE);
3544               }
3545             }
3546             // jprovider is null when executing 'New View'
3547             if (pdbid.getFile() != null && jprovider != null)
3548             {
3549               if (!pdbloaded.containsKey(pdbid.getFile()))
3550               {
3551                 entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
3552                         pdbid.getFile()));
3553               }
3554               else
3555               {
3556                 entry.setFile(pdbloaded.get(pdbid.getId()).toString());
3557               }
3558             }
3559             /*
3560             if (pdbid.getPdbentryItem() != null)
3561             {
3562               for (PdbentryItem item : pdbid.getPdbentryItem())
3563               {
3564                 for (Property pr : item.getProperty())
3565                 {
3566                   entry.setProperty(pr.getName(), pr.getValue());
3567                 }
3568               }
3569             }
3570             */
3571             for (Property prop : pdbid.getProperty())
3572             {
3573               entry.setProperty(prop.getName(), prop.getValue());
3574             }
3575             StructureSelectionManager
3576                     .getStructureSelectionManager(Desktop.instance)
3577                     .registerPDBEntry(entry);
3578             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3579             if (al.getSequenceAt(i).getDatasetSequence() != null)
3580             {
3581               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3582             }
3583             else
3584             {
3585               al.getSequenceAt(i).addPDBId(entry);
3586             }
3587           }
3588         }
3589       }
3590     } // end !multipleview
3591
3592     // ///////////////////////////////
3593     // LOAD SEQUENCE MAPPINGS
3594
3595     if (vamsasSet.getAlcodonFrame().size() > 0)
3596     {
3597       // TODO Potentially this should only be done once for all views of an
3598       // alignment
3599       List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
3600       for (int i = 0; i < alc.size(); i++)
3601       {
3602         AlignedCodonFrame cf = new AlignedCodonFrame();
3603         if (alc.get(i).getAlcodMap().size() > 0)
3604         {
3605           List<AlcodMap> maps = alc.get(i).getAlcodMap();
3606           for (int m = 0; m < maps.size(); m++)
3607           {
3608             AlcodMap map = maps.get(m);
3609             SequenceI dnaseq = seqRefIds.get(map.getDnasq());
3610             // Load Mapping
3611             jalview.datamodel.Mapping mapping = null;
3612             // attach to dna sequence reference.
3613             if (map.getMapping() != null)
3614             {
3615               mapping = addMapping(map.getMapping());
3616               if (dnaseq != null && mapping.getTo() != null)
3617               {
3618                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3619               }
3620               else
3621               {
3622                 // defer to later
3623                 frefedSequence.add(
3624                         newAlcodMapRef(map.getDnasq(), cf, mapping));
3625               }
3626             }
3627           }
3628           al.addCodonFrame(cf);
3629         }
3630       }
3631     }
3632
3633     // ////////////////////////////////
3634     // LOAD ANNOTATIONS
3635     List<JvAnnotRow> autoAlan = new ArrayList<>();
3636
3637     /*
3638      * store any annotations which forward reference a group's ID
3639      */
3640     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
3641
3642     if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
3643     {
3644       List<Annotation> an = vamsasSet.getAnnotation();
3645
3646       for (int i = 0; i < an.size(); i++)
3647       {
3648         Annotation annotation = an.get(i);
3649
3650         /**
3651          * test if annotation is automatically calculated for this view only
3652          */
3653         boolean autoForView = false;
3654         if (annotation.getLabel().equals("Quality")
3655                 || annotation.getLabel().equals("Conservation")
3656                 || annotation.getLabel().equals("Consensus"))
3657         {
3658           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3659           autoForView = true;
3660           // JAXB has no has() test; schema defaults value to false
3661           // if (!annotation.hasAutoCalculated())
3662           // {
3663           // annotation.setAutoCalculated(true);
3664           // }
3665         }
3666         if (autoForView || annotation.isAutoCalculated())
3667         {
3668           // remove ID - we don't recover annotation from other views for
3669           // view-specific annotation
3670           annotation.setId(null);
3671         }
3672
3673         // set visibility for other annotation in this view
3674         String annotationId = annotation.getId();
3675         if (annotationId != null && annotationIds.containsKey(annotationId))
3676         {
3677           AlignmentAnnotation jda = annotationIds.get(annotationId);
3678           // in principle Visible should always be true for annotation displayed
3679           // in multiple views
3680           if (annotation.isVisible() != null)
3681           {
3682             jda.visible = annotation.isVisible();
3683           }
3684
3685           al.addAnnotation(jda);
3686
3687           continue;
3688         }
3689         // Construct new annotation from model.
3690         List<AnnotationElement> ae = annotation.getAnnotationElement();
3691         jalview.datamodel.Annotation[] anot = null;
3692         java.awt.Color firstColour = null;
3693         int anpos;
3694         if (!annotation.isScoreOnly())
3695         {
3696           anot = new jalview.datamodel.Annotation[al.getWidth()];
3697           for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
3698           {
3699             AnnotationElement annElement = ae.get(aa);
3700             anpos = annElement.getPosition();
3701
3702             if (anpos >= anot.length)
3703             {
3704               continue;
3705             }
3706
3707             float value = safeFloat(annElement.getValue());
3708             anot[anpos] = new jalview.datamodel.Annotation(
3709                     annElement.getDisplayCharacter(),
3710                     annElement.getDescription(),
3711                     (annElement.getSecondaryStructure() == null
3712                             || annElement.getSecondaryStructure()
3713                                     .length() == 0)
3714                                             ? ' '
3715                                             : annElement
3716                                                     .getSecondaryStructure()
3717                                                     .charAt(0),
3718                     value);
3719             anot[anpos].colour = new Color(safeInt(annElement.getColour()));
3720             if (firstColour == null)
3721             {
3722               firstColour = anot[anpos].colour;
3723             }
3724           }
3725         }
3726         jalview.datamodel.AlignmentAnnotation jaa = null;
3727
3728         if (annotation.isGraph())
3729         {
3730           float llim = 0, hlim = 0;
3731           // if (autoForView || an[i].isAutoCalculated()) {
3732           // hlim=11f;
3733           // }
3734           jaa = new jalview.datamodel.AlignmentAnnotation(
3735                   annotation.getLabel(), annotation.getDescription(), anot,
3736                   llim, hlim, safeInt(annotation.getGraphType()));
3737
3738           jaa.graphGroup = safeInt(annotation.getGraphGroup());
3739           jaa._linecolour = firstColour;
3740           if (annotation.getThresholdLine() != null)
3741           {
3742             jaa.setThreshold(new jalview.datamodel.GraphLine(
3743                     safeFloat(annotation.getThresholdLine().getValue()),
3744                     annotation.getThresholdLine().getLabel(),
3745                     new java.awt.Color(safeInt(
3746                             annotation.getThresholdLine().getColour()))));
3747           }
3748           if (autoForView || annotation.isAutoCalculated())
3749           {
3750             // Hardwire the symbol display line to ensure that labels for
3751             // histograms are displayed
3752             jaa.hasText = true;
3753           }
3754         }
3755         else
3756         {
3757           jaa = new jalview.datamodel.AlignmentAnnotation(
3758                   annotation.getLabel(), annotation.getDescription(), anot);
3759           jaa._linecolour = firstColour;
3760         }
3761         // register new annotation
3762         if (annotation.getId() != null)
3763         {
3764           annotationIds.put(annotation.getId(), jaa);
3765           jaa.annotationId = annotation.getId();
3766         }
3767         // recover sequence association
3768         String sequenceRef = annotation.getSequenceRef();
3769         if (sequenceRef != null)
3770         {
3771           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3772           SequenceI sequence = seqRefIds.get(sequenceRef);
3773           if (sequence == null)
3774           {
3775             // in pre-2.9 projects sequence ref is to sequence name
3776             sequence = al.findName(sequenceRef);
3777           }
3778           if (sequence != null)
3779           {
3780             jaa.createSequenceMapping(sequence, 1, true);
3781             sequence.addAlignmentAnnotation(jaa);
3782           }
3783         }
3784         // and make a note of any group association
3785         if (annotation.getGroupRef() != null
3786                 && annotation.getGroupRef().length() > 0)
3787         {
3788           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3789                   .get(annotation.getGroupRef());
3790           if (aal == null)
3791           {
3792             aal = new ArrayList<>();
3793             groupAnnotRefs.put(annotation.getGroupRef(), aal);
3794           }
3795           aal.add(jaa);
3796         }
3797
3798         if (annotation.getScore() != null)
3799         {
3800           jaa.setScore(annotation.getScore().doubleValue());
3801         }
3802         if (annotation.isVisible() != null)
3803         {
3804           jaa.visible = annotation.isVisible().booleanValue();
3805         }
3806
3807         if (annotation.isCentreColLabels() != null)
3808         {
3809           jaa.centreColLabels = annotation.isCentreColLabels()
3810                   .booleanValue();
3811         }
3812
3813         if (annotation.isScaleColLabels() != null)
3814         {
3815           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
3816         }
3817         if (annotation.isAutoCalculated())
3818         {
3819           // newer files have an 'autoCalculated' flag and store calculation
3820           // state in viewport properties
3821           jaa.autoCalculated = true; // means annotation will be marked for
3822           // update at end of load.
3823         }
3824         if (annotation.getGraphHeight() != null)
3825         {
3826           jaa.graphHeight = annotation.getGraphHeight().intValue();
3827         }
3828         jaa.belowAlignment = annotation.isBelowAlignment();
3829         jaa.setCalcId(annotation.getCalcId());
3830         if (annotation.getProperty().size() > 0)
3831         {
3832           for (Annotation.Property prop : annotation
3833                   .getProperty())
3834           {
3835             jaa.setProperty(prop.getName(), prop.getValue());
3836           }
3837         }
3838         if (jaa.autoCalculated)
3839         {
3840           autoAlan.add(new JvAnnotRow(i, jaa));
3841         }
3842         else
3843         // if (!autoForView)
3844         {
3845           // add autocalculated group annotation and any user created annotation
3846           // for the view
3847           al.addAnnotation(jaa);
3848         }
3849       }
3850     }
3851     // ///////////////////////
3852     // LOAD GROUPS
3853     // Create alignment markup and styles for this view
3854     if (jalviewModel.getJGroup().size() > 0)
3855     {
3856       List<JGroup> groups = jalviewModel.getJGroup();
3857       boolean addAnnotSchemeGroup = false;
3858       for (int i = 0; i < groups.size(); i++)
3859       {
3860         JGroup jGroup = groups.get(i);
3861         ColourSchemeI cs = null;
3862         if (jGroup.getColour() != null)
3863         {
3864           if (jGroup.getColour().startsWith("ucs"))
3865           {
3866             cs = getUserColourScheme(jalviewModel, jGroup.getColour());
3867           }
3868           else if (jGroup.getColour().equals("AnnotationColourGradient")
3869                   && jGroup.getAnnotationColours() != null)
3870           {
3871             addAnnotSchemeGroup = true;
3872           }
3873           else
3874           {
3875             cs = ColourSchemeProperty.getColourScheme(null, al,
3876                     jGroup.getColour());
3877           }
3878         }
3879         int pidThreshold = safeInt(jGroup.getPidThreshold());
3880
3881         Vector<SequenceI> seqs = new Vector<>();
3882
3883         for (int s = 0; s < jGroup.getSeq().size(); s++)
3884         {
3885           String seqId = jGroup.getSeq().get(s);
3886           SequenceI ts = seqRefIds.get(seqId);
3887
3888           if (ts != null)
3889           {
3890             seqs.addElement(ts);
3891           }
3892         }
3893
3894         if (seqs.size() < 1)
3895         {
3896           continue;
3897         }
3898
3899         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3900                 safeBoolean(jGroup.isDisplayBoxes()),
3901                 safeBoolean(jGroup.isDisplayText()),
3902                 safeBoolean(jGroup.isColourText()),
3903                 safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
3904         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
3905         sg.getGroupColourScheme()
3906                 .setConservationInc(safeInt(jGroup.getConsThreshold()));
3907         sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
3908
3909         sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
3910         sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
3911         sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
3912         sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
3913         // attributes with a default in the schema are never null
3914           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3915           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3916           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3917         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
3918         if (jGroup.getConsThreshold() != null
3919                 && jGroup.getConsThreshold().intValue() != 0)
3920         {
3921           Conservation c = new Conservation("All", sg.getSequences(null), 0,
3922                   sg.getWidth() - 1);
3923           c.calculate();
3924           c.verdict(false, 25);
3925           sg.cs.setConservation(c);
3926         }
3927
3928         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3929         {
3930           // re-instate unique group/annotation row reference
3931           List<AlignmentAnnotation> jaal = groupAnnotRefs
3932                   .get(jGroup.getId());
3933           if (jaal != null)
3934           {
3935             for (AlignmentAnnotation jaa : jaal)
3936             {
3937               jaa.groupRef = sg;
3938               if (jaa.autoCalculated)
3939               {
3940                 // match up and try to set group autocalc alignment row for this
3941                 // annotation
3942                 if (jaa.label.startsWith("Consensus for "))
3943                 {
3944                   sg.setConsensus(jaa);
3945                 }
3946                 // match up and try to set group autocalc alignment row for this
3947                 // annotation
3948                 if (jaa.label.startsWith("Conservation for "))
3949                 {
3950                   sg.setConservationRow(jaa);
3951                 }
3952               }
3953             }
3954           }
3955         }
3956         al.addGroup(sg);
3957         if (addAnnotSchemeGroup)
3958         {
3959           // reconstruct the annotation colourscheme
3960           sg.setColourScheme(constructAnnotationColour(
3961                   jGroup.getAnnotationColours(), null, al, jalviewModel, false));
3962         }
3963       }
3964     }
3965     if (view == null)
3966     {
3967       // only dataset in this model, so just return.
3968       return null;
3969     }
3970     // ///////////////////////////////
3971     // LOAD VIEWPORT
3972
3973     AlignFrame af = null;
3974     AlignViewport av = null;
3975     // now check to see if we really need to create a new viewport.
3976     if (multipleView && viewportsAdded.size() == 0)
3977     {
3978       // We recovered an alignment for which a viewport already exists.
3979       // TODO: fix up any settings necessary for overlaying stored state onto
3980       // state recovered from another document. (may not be necessary).
3981       // we may need a binding from a viewport in memory to one recovered from
3982       // XML.
3983       // and then recover its containing af to allow the settings to be applied.
3984       // TODO: fix for vamsas demo
3985       System.err.println(
3986               "About to recover a viewport for existing alignment: Sequence set ID is "
3987                       + uniqueSeqSetId);
3988       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3989       if (seqsetobj != null)
3990       {
3991         if (seqsetobj instanceof String)
3992         {
3993           uniqueSeqSetId = (String) seqsetobj;
3994           System.err.println(
3995                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3996                           + uniqueSeqSetId);
3997         }
3998         else
3999         {
4000           System.err.println(
4001                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
4002         }
4003
4004       }
4005     }
4006     /**
4007      * indicate that annotation colours are applied across all groups (pre
4008      * Jalview 2.8.1 behaviour)
4009      */
4010     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
4011             jalviewModel.getVersion());
4012
4013     AlignmentPanel ap = null;
4014     boolean isnewview = true;
4015     if (viewId != null)
4016     {
4017       // Check to see if this alignment already has a view id == viewId
4018       jalview.gui.AlignmentPanel views[] = Desktop
4019               .getAlignmentPanels(uniqueSeqSetId);
4020       if (views != null && views.length > 0)
4021       {
4022         for (int v = 0; v < views.length; v++)
4023         {
4024           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
4025           {
4026             // recover the existing alignpanel, alignframe, viewport
4027             af = views[v].alignFrame;
4028             av = views[v].av;
4029             ap = views[v];
4030             // TODO: could even skip resetting view settings if we don't want to
4031             // change the local settings from other jalview processes
4032             isnewview = false;
4033           }
4034         }
4035       }
4036     }
4037
4038     if (isnewview)
4039     {
4040       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
4041               uniqueSeqSetId, viewId, autoAlan);
4042       av = af.getViewport();
4043       ap = af.alignPanel;
4044     }
4045
4046     /*
4047      * Load any trees, PDB structures and viewers
4048      * 
4049      * Not done if flag is false (when this method is used for New View)
4050      */
4051     if (loadTreesAndStructures)
4052     {
4053       loadTrees(jalviewModel, view, af, av, ap);
4054       loadPCAViewers(jalviewModel, ap);
4055       loadPDBStructures(jprovider, jseqs, af, ap);
4056       loadRnaViewers(jprovider, jseqs, ap);
4057     }
4058     // and finally return.
4059     return af;
4060   }
4061
4062   /**
4063    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
4064    * panel is restored from separate jar entries, two (gapped and trimmed) per
4065    * sequence and secondary structure.
4066    * 
4067    * Currently each viewer shows just one sequence and structure (gapped and
4068    * trimmed), however this method is designed to support multiple sequences or
4069    * structures in viewers if wanted in future.
4070    * 
4071    * @param jprovider
4072    * @param jseqs
4073    * @param ap
4074    */
4075   private void loadRnaViewers(jarInputStreamProvider jprovider,
4076           List<JSeq> jseqs, AlignmentPanel ap)
4077   {
4078     /*
4079      * scan the sequences for references to viewers; create each one the first
4080      * time it is referenced, add Rna models to existing viewers
4081      */
4082     for (JSeq jseq : jseqs)
4083     {
4084       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
4085       {
4086         RnaViewer viewer = jseq.getRnaViewer().get(i);
4087         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
4088                 ap);
4089
4090         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
4091         {
4092           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
4093           SequenceI seq = seqRefIds.get(jseq.getId());
4094           AlignmentAnnotation ann = this.annotationIds
4095                   .get(ss.getAnnotationId());
4096
4097           /*
4098            * add the structure to the Varna display (with session state copied
4099            * from the jar to a temporary file)
4100            */
4101           boolean gapped = safeBoolean(ss.isGapped());
4102           String rnaTitle = ss.getTitle();
4103           String sessionState = ss.getViewerState();
4104           String tempStateFile = copyJarEntry(jprovider, sessionState,
4105                   "varna", null);
4106           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
4107           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
4108         }
4109         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
4110       }
4111     }
4112   }
4113
4114   /**
4115    * Locate and return an already instantiated matching AppVarna, or create one
4116    * if not found
4117    * 
4118    * @param viewer
4119    * @param viewIdSuffix
4120    * @param ap
4121    * @return
4122    */
4123   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
4124           String viewIdSuffix, AlignmentPanel ap)
4125   {
4126     /*
4127      * on each load a suffix is appended to the saved viewId, to avoid conflicts
4128      * if load is repeated
4129      */
4130     String postLoadId = viewer.getViewId() + viewIdSuffix;
4131     for (JInternalFrame frame : getAllFrames())
4132     {
4133       if (frame instanceof AppVarna)
4134       {
4135         AppVarna varna = (AppVarna) frame;
4136         if (postLoadId.equals(varna.getViewId()))
4137         {
4138           // this viewer is already instantiated
4139           // could in future here add ap as another 'parent' of the
4140           // AppVarna window; currently just 1-to-many
4141           return varna;
4142         }
4143       }
4144     }
4145
4146     /*
4147      * viewer not found - make it
4148      */
4149     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
4150             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
4151             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
4152             safeInt(viewer.getDividerLocation()));
4153     AppVarna varna = new AppVarna(model, ap);
4154
4155     return varna;
4156   }
4157
4158   /**
4159    * Load any saved trees
4160    * 
4161    * @param jm
4162    * @param view
4163    * @param af
4164    * @param av
4165    * @param ap
4166    */
4167   protected void loadTrees(JalviewModel jm, Viewport view,
4168           AlignFrame af, AlignViewport av, AlignmentPanel ap)
4169   {
4170     // TODO result of automated refactoring - are all these parameters needed?
4171     try
4172     {
4173       for (int t = 0; t < jm.getTree().size(); t++)
4174       {
4175
4176         Tree tree = jm.getTree().get(t);
4177
4178         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
4179         if (tp == null)
4180         {
4181           tp = af.showNewickTree(new NewickFile(tree.getNewick()),
4182                   tree.getTitle(), safeInt(tree.getWidth()),
4183                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
4184                   safeInt(tree.getYpos()));
4185           if (tree.getId() != null)
4186           {
4187             // perhaps bind the tree id to something ?
4188           }
4189         }
4190         else
4191         {
4192           // update local tree attributes ?
4193           // TODO: should check if tp has been manipulated by user - if so its
4194           // settings shouldn't be modified
4195           tp.setTitle(tree.getTitle());
4196           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
4197                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
4198                   safeInt(tree.getHeight())));
4199           tp.setViewport(av); // af.viewport;
4200           // TODO: verify 'associate with all views' works still
4201           tp.getTreeCanvas().setViewport(av); // af.viewport;
4202           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
4203         }
4204         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
4205         if (tp == null)
4206         {
4207           warn("There was a problem recovering stored Newick tree: \n"
4208                   + tree.getNewick());
4209           continue;
4210         }
4211
4212         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
4213         tp.fitToWindow_actionPerformed(null);
4214
4215         if (tree.getFontName() != null)
4216         {
4217           tp.setTreeFont(
4218                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
4219                           safeInt(tree.getFontSize())));
4220         }
4221         else
4222         {
4223           tp.setTreeFont(
4224                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
4225                           safeInt(view.getFontSize())));
4226         }
4227
4228         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
4229         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
4230         tp.showDistances(safeBoolean(tree.isShowDistances()));
4231
4232         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
4233
4234         if (safeBoolean(tree.isCurrentTree()))
4235         {
4236           af.getViewport().setCurrentTree(tp.getTree());
4237         }
4238       }
4239
4240     } catch (Exception ex)
4241     {
4242       ex.printStackTrace();
4243     }
4244   }
4245
4246   /**
4247    * Load and link any saved structure viewers.
4248    * 
4249    * @param jprovider
4250    * @param jseqs
4251    * @param af
4252    * @param ap
4253    */
4254   protected void loadPDBStructures(jarInputStreamProvider jprovider,
4255           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
4256   {
4257     /*
4258      * Run through all PDB ids on the alignment, and collect mappings between
4259      * distinct view ids and all sequences referring to that view.
4260      */
4261     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
4262
4263     for (int i = 0; i < jseqs.size(); i++)
4264     {
4265       JSeq jseq = jseqs.get(i);
4266       if (jseq.getPdbids().size() > 0)
4267       {
4268         List<Pdbids> ids = jseq.getPdbids();
4269         for (int p = 0; p < ids.size(); p++)
4270         {
4271           Pdbids pdbid = ids.get(p);
4272           final int structureStateCount = pdbid.getStructureState().size();
4273           for (int s = 0; s < structureStateCount; s++)
4274           {
4275             // check to see if we haven't already created this structure view
4276             final StructureState structureState = pdbid
4277                     .getStructureState().get(s);
4278             String sviewid = (structureState.getViewId() == null) ? null
4279                     : structureState.getViewId() + uniqueSetSuffix;
4280             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4281             // Originally : pdbid.getFile()
4282             // : TODO: verify external PDB file recovery still works in normal
4283             // jalview project load
4284             jpdb.setFile(
4285                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4286             jpdb.setId(pdbid.getId());
4287
4288             int x = safeInt(structureState.getXpos());
4289             int y = safeInt(structureState.getYpos());
4290             int width = safeInt(structureState.getWidth());
4291             int height = safeInt(structureState.getHeight());
4292
4293             // Probably don't need to do this anymore...
4294             // Desktop.desktop.getComponentAt(x, y);
4295             // TODO: NOW: check that this recovers the PDB file correctly.
4296             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4297                     pdbid.getFile());
4298             jalview.datamodel.SequenceI seq = seqRefIds
4299                     .get(jseq.getId() + "");
4300             if (sviewid == null)
4301             {
4302               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4303                       + height;
4304             }
4305             if (!structureViewers.containsKey(sviewid))
4306             {
4307               structureViewers.put(sviewid,
4308                       new StructureViewerModel(x, y, width, height, false,
4309                               false, true, structureState.getViewId(),
4310                               structureState.getType()));
4311               // Legacy pre-2.7 conversion JAL-823 :
4312               // do not assume any view has to be linked for colour by
4313               // sequence
4314             }
4315
4316             // assemble String[] { pdb files }, String[] { id for each
4317             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4318             // seqs_file 2}, boolean[] {
4319             // linkAlignPanel,superposeWithAlignpanel}} from hash
4320             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4321             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4322                     || structureState.isAlignwithAlignPanel());
4323
4324             /*
4325              * Default colour by linked panel to false if not specified (e.g.
4326              * for pre-2.7 projects)
4327              */
4328             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4329             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4330             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4331
4332             /*
4333              * Default colour by viewer to true if not specified (e.g. for
4334              * pre-2.7 projects)
4335              */
4336             boolean colourByViewer = jmoldat.isColourByViewer();
4337             colourByViewer &= structureState.isColourByJmol();
4338             jmoldat.setColourByViewer(colourByViewer);
4339
4340             if (jmoldat.getStateData().length() < structureState
4341                     .getValue()/*Content()*/.length())
4342             {
4343               jmoldat.setStateData(structureState.getValue());// Content());
4344             }
4345             if (pdbid.getFile() != null)
4346             {
4347               File mapkey = new File(pdbid.getFile());
4348               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4349               if (seqstrmaps == null)
4350               {
4351                 jmoldat.getFileData().put(mapkey,
4352                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4353                                 pdbid.getId()));
4354               }
4355               if (!seqstrmaps.getSeqList().contains(seq))
4356               {
4357                 seqstrmaps.getSeqList().add(seq);
4358                 // TODO and chains?
4359               }
4360             }
4361             else
4362             {
4363               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");
4364               warn(errorMessage);
4365             }
4366           }
4367         }
4368       }
4369     }
4370     // Instantiate the associated structure views
4371     for (Entry<String, StructureViewerModel> entry : structureViewers
4372             .entrySet())
4373     {
4374       try
4375       {
4376         createOrLinkStructureViewer(entry, af, ap, jprovider);
4377       } catch (Exception e)
4378       {
4379         System.err.println(
4380                 "Error loading structure viewer: " + e.getMessage());
4381         // failed - try the next one
4382       }
4383     }
4384   }
4385
4386   /**
4387    * 
4388    * @param viewerData
4389    * @param af
4390    * @param ap
4391    * @param jprovider
4392    */
4393   protected void createOrLinkStructureViewer(
4394           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4395           AlignmentPanel ap, jarInputStreamProvider jprovider)
4396   {
4397     final StructureViewerModel stateData = viewerData.getValue();
4398
4399     /*
4400      * Search for any viewer windows already open from other alignment views
4401      * that exactly match the stored structure state
4402      */
4403     StructureViewerBase comp = findMatchingViewer(viewerData);
4404
4405     if (comp != null)
4406     {
4407       linkStructureViewer(ap, comp, stateData);
4408       return;
4409     }
4410
4411     /*
4412      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
4413      * "viewer_"+stateData.viewId
4414      */
4415     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
4416     {
4417       createChimeraViewer(viewerData, af, jprovider);
4418     }
4419     else
4420     {
4421       /*
4422        * else Jmol (if pre-2.9, stateData contains JMOL state string)
4423        */
4424       createJmolViewer(viewerData, af, jprovider);
4425     }
4426   }
4427
4428   /**
4429    * Create a new Chimera viewer.
4430    * 
4431    * @param data
4432    * @param af
4433    * @param jprovider
4434    */
4435   protected void createChimeraViewer(
4436           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4437           jarInputStreamProvider jprovider)
4438   {
4439     StructureViewerModel data = viewerData.getValue();
4440     String chimeraSessionFile = data.getStateData();
4441
4442     /*
4443      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
4444      * 
4445      * NB this is the 'saved' viewId as in the project file XML, _not_ the
4446      * 'uniquified' sviewid used to reconstruct the viewer here
4447      */
4448     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
4449     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
4450             "chimera", null);
4451
4452     Set<Entry<File, StructureData>> fileData = data.getFileData()
4453             .entrySet();
4454     List<PDBEntry> pdbs = new ArrayList<>();
4455     List<SequenceI[]> allseqs = new ArrayList<>();
4456     for (Entry<File, StructureData> pdb : fileData)
4457     {
4458       String filePath = pdb.getValue().getFilePath();
4459       String pdbId = pdb.getValue().getPdbId();
4460       // pdbs.add(new PDBEntry(filePath, pdbId));
4461       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4462       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4463       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4464       allseqs.add(seqs);
4465     }
4466
4467     boolean colourByChimera = data.isColourByViewer();
4468     boolean colourBySequence = data.isColourWithAlignPanel();
4469
4470     // TODO use StructureViewer as a factory here, see JAL-1761
4471     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4472     final SequenceI[][] seqsArray = allseqs
4473             .toArray(new SequenceI[allseqs.size()][]);
4474     String newViewId = viewerData.getKey();
4475
4476     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4477             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4478             colourBySequence, newViewId);
4479     cvf.setSize(data.getWidth(), data.getHeight());
4480     cvf.setLocation(data.getX(), data.getY());
4481   }
4482
4483   /**
4484    * Create a new Jmol window. First parse the Jmol state to translate filenames
4485    * loaded into the view, and record the order in which files are shown in the
4486    * Jmol view, so we can add the sequence mappings in same order.
4487    * 
4488    * @param viewerData
4489    * @param af
4490    * @param jprovider
4491    */
4492   protected void createJmolViewer(
4493           final Entry<String, StructureViewerModel> viewerData,
4494           AlignFrame af, jarInputStreamProvider jprovider)
4495   {
4496     final StructureViewerModel svattrib = viewerData.getValue();
4497     String state = svattrib.getStateData();
4498
4499     /*
4500      * Pre-2.9: state element value is the Jmol state string
4501      * 
4502      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4503      * + viewId
4504      */
4505     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4506     {
4507       state = readJarEntry(jprovider,
4508               getViewerJarEntryName(svattrib.getViewId()));
4509     }
4510
4511     List<String> pdbfilenames = new ArrayList<>();
4512     List<SequenceI[]> seqmaps = new ArrayList<>();
4513     List<String> pdbids = new ArrayList<>();
4514     StringBuilder newFileLoc = new StringBuilder(64);
4515     int cp = 0, ncp, ecp;
4516     Map<File, StructureData> oldFiles = svattrib.getFileData();
4517     while ((ncp = state.indexOf("load ", cp)) > -1)
4518     {
4519       do
4520       {
4521         // look for next filename in load statement
4522         newFileLoc.append(state.substring(cp,
4523                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4524         String oldfilenam = state.substring(ncp,
4525                 ecp = state.indexOf("\"", ncp));
4526         // recover the new mapping data for this old filename
4527         // have to normalize filename - since Jmol and jalview do
4528         // filename
4529         // translation differently.
4530         StructureData filedat = oldFiles.get(new File(oldfilenam));
4531         if (filedat == null)
4532         {
4533           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4534           filedat = oldFiles.get(new File(reformatedOldFilename));
4535         }
4536         newFileLoc.append(Platform.escapeBackslashes(filedat.getFilePath()));
4537         pdbfilenames.add(filedat.getFilePath());
4538         pdbids.add(filedat.getPdbId());
4539         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4540         newFileLoc.append("\"");
4541         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4542                       // look for next file statement.
4543       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4544     }
4545     if (cp > 0)
4546     {
4547       // just append rest of state
4548       newFileLoc.append(state.substring(cp));
4549     }
4550     else
4551     {
4552       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4553       newFileLoc = new StringBuilder(state);
4554       newFileLoc.append("; load append ");
4555       for (File id : oldFiles.keySet())
4556       {
4557         // add this and any other pdb files that should be present in
4558         // the viewer
4559         StructureData filedat = oldFiles.get(id);
4560         newFileLoc.append(filedat.getFilePath());
4561         pdbfilenames.add(filedat.getFilePath());
4562         pdbids.add(filedat.getPdbId());
4563         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4564         newFileLoc.append(" \"");
4565         newFileLoc.append(filedat.getFilePath());
4566         newFileLoc.append("\"");
4567
4568       }
4569       newFileLoc.append(";");
4570     }
4571
4572     if (newFileLoc.length() == 0)
4573     {
4574       return;
4575     }
4576     int histbug = newFileLoc.indexOf("history = ");
4577     if (histbug > -1)
4578     {
4579       /*
4580        * change "history = [true|false];" to "history = [1|0];"
4581        */
4582       histbug += 10;
4583       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4584       String val = (diff == -1) ? null
4585               : newFileLoc.substring(histbug, diff);
4586       if (val != null && val.length() >= 4)
4587       {
4588         if (val.contains("e")) // eh? what can it be?
4589         {
4590           if (val.trim().equals("true"))
4591           {
4592             val = "1";
4593           }
4594           else
4595           {
4596             val = "0";
4597           }
4598           newFileLoc.replace(histbug, diff, val);
4599         }
4600       }
4601     }
4602
4603     final String[] pdbf = pdbfilenames
4604             .toArray(new String[pdbfilenames.size()]);
4605     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4606     final SequenceI[][] sq = seqmaps
4607             .toArray(new SequenceI[seqmaps.size()][]);
4608     final String fileloc = newFileLoc.toString();
4609     final String sviewid = viewerData.getKey();
4610     final AlignFrame alf = af;
4611     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4612             svattrib.getWidth(), svattrib.getHeight());
4613     try
4614     {
4615       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4616       {
4617         @Override
4618         public void run()
4619         {
4620           JalviewStructureDisplayI sview = null;
4621           try
4622           {
4623             sview = new StructureViewer(
4624                     alf.alignPanel.getStructureSelectionManager())
4625                             .createView(StructureViewer.ViewerType.JMOL,
4626                                     pdbf, id, sq, alf.alignPanel, svattrib,
4627                                     fileloc, rect, sviewid);
4628             addNewStructureViewer(sview);
4629           } catch (OutOfMemoryError ex)
4630           {
4631             new OOMWarning("restoring structure view for PDB id " + id,
4632                     (OutOfMemoryError) ex.getCause());
4633             if (sview != null && sview.isVisible())
4634             {
4635               sview.closeViewer(false);
4636               sview.setVisible(false);
4637               sview.dispose();
4638             }
4639           }
4640         }
4641       });
4642     } catch (InvocationTargetException ex)
4643     {
4644       warn("Unexpected error when opening Jmol view.", ex);
4645
4646     } catch (InterruptedException e)
4647     {
4648       // e.printStackTrace();
4649     }
4650
4651   }
4652
4653   /**
4654    * Generates a name for the entry in the project jar file to hold state
4655    * information for a structure viewer
4656    * 
4657    * @param viewId
4658    * @return
4659    */
4660   protected String getViewerJarEntryName(String viewId)
4661   {
4662     return VIEWER_PREFIX + viewId;
4663   }
4664
4665   /**
4666    * Returns any open frame that matches given structure viewer data. The match
4667    * is based on the unique viewId, or (for older project versions) the frame's
4668    * geometry.
4669    * 
4670    * @param viewerData
4671    * @return
4672    */
4673   protected StructureViewerBase findMatchingViewer(
4674           Entry<String, StructureViewerModel> viewerData)
4675   {
4676     final String sviewid = viewerData.getKey();
4677     final StructureViewerModel svattrib = viewerData.getValue();
4678     StructureViewerBase comp = null;
4679     JInternalFrame[] frames = getAllFrames();
4680     for (JInternalFrame frame : frames)
4681     {
4682       if (frame instanceof StructureViewerBase)
4683       {
4684         /*
4685          * Post jalview 2.4 schema includes structure view id
4686          */
4687         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4688                 .equals(sviewid))
4689         {
4690           comp = (StructureViewerBase) frame;
4691           break; // break added in 2.9
4692         }
4693         /*
4694          * Otherwise test for matching position and size of viewer frame
4695          */
4696         else if (frame.getX() == svattrib.getX()
4697                 && frame.getY() == svattrib.getY()
4698                 && frame.getHeight() == svattrib.getHeight()
4699                 && frame.getWidth() == svattrib.getWidth())
4700         {
4701           comp = (StructureViewerBase) frame;
4702           // no break in faint hope of an exact match on viewId
4703         }
4704       }
4705     }
4706     return comp;
4707   }
4708
4709   /**
4710    * Link an AlignmentPanel to an existing structure viewer.
4711    * 
4712    * @param ap
4713    * @param viewer
4714    * @param oldFiles
4715    * @param useinViewerSuperpos
4716    * @param usetoColourbyseq
4717    * @param viewerColouring
4718    */
4719   protected void linkStructureViewer(AlignmentPanel ap,
4720           StructureViewerBase viewer, StructureViewerModel stateData)
4721   {
4722     // NOTE: if the jalview project is part of a shared session then
4723     // view synchronization should/could be done here.
4724
4725     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4726     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4727     final boolean viewerColouring = stateData.isColourByViewer();
4728     Map<File, StructureData> oldFiles = stateData.getFileData();
4729
4730     /*
4731      * Add mapping for sequences in this view to an already open viewer
4732      */
4733     final AAStructureBindingModel binding = viewer.getBinding();
4734     for (File id : oldFiles.keySet())
4735     {
4736       // add this and any other pdb files that should be present in the
4737       // viewer
4738       StructureData filedat = oldFiles.get(id);
4739       String pdbFile = filedat.getFilePath();
4740       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4741       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4742               null);
4743       binding.addSequenceForStructFile(pdbFile, seq);
4744     }
4745     // and add the AlignmentPanel's reference to the view panel
4746     viewer.addAlignmentPanel(ap);
4747     if (useinViewerSuperpos)
4748     {
4749       viewer.useAlignmentPanelForSuperposition(ap);
4750     }
4751     else
4752     {
4753       viewer.excludeAlignmentPanelForSuperposition(ap);
4754     }
4755     if (usetoColourbyseq)
4756     {
4757       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4758     }
4759     else
4760     {
4761       viewer.excludeAlignmentPanelForColourbyseq(ap);
4762     }
4763   }
4764
4765   /**
4766    * Get all frames within the Desktop.
4767    * 
4768    * @return
4769    */
4770   protected JInternalFrame[] getAllFrames()
4771   {
4772     JInternalFrame[] frames = null;
4773     // TODO is this necessary - is it safe - risk of hanging?
4774     do
4775     {
4776       try
4777       {
4778         frames = Desktop.desktop.getAllFrames();
4779       } catch (ArrayIndexOutOfBoundsException e)
4780       {
4781         // occasional No such child exceptions are thrown here...
4782         try
4783         {
4784           Thread.sleep(10);
4785         } catch (InterruptedException f)
4786         {
4787         }
4788       }
4789     } while (frames == null);
4790     return frames;
4791   }
4792
4793   /**
4794    * Answers true if 'version' is equal to or later than 'supported', where each
4795    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4796    * changes. Development and test values for 'version' are leniently treated
4797    * i.e. answer true.
4798    * 
4799    * @param supported
4800    *          - minimum version we are comparing against
4801    * @param version
4802    *          - version of data being processsed
4803    * @return
4804    */
4805   public static boolean isVersionStringLaterThan(String supported,
4806           String version)
4807   {
4808     if (supported == null || version == null
4809             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4810             || version.equalsIgnoreCase("Test")
4811             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4812     {
4813       System.err.println("Assuming project file with "
4814               + (version == null ? "null" : version)
4815               + " is compatible with Jalview version " + supported);
4816       return true;
4817     }
4818     else
4819     {
4820       return StringUtils.compareVersions(version, supported, "b") >= 0;
4821     }
4822   }
4823
4824   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4825
4826   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4827   {
4828     if (newStructureViewers != null)
4829     {
4830       sview.getBinding().setFinishedLoadingFromArchive(false);
4831       newStructureViewers.add(sview);
4832     }
4833   }
4834
4835   protected void setLoadingFinishedForNewStructureViewers()
4836   {
4837     if (newStructureViewers != null)
4838     {
4839       for (JalviewStructureDisplayI sview : newStructureViewers)
4840       {
4841         sview.getBinding().setFinishedLoadingFromArchive(true);
4842       }
4843       newStructureViewers.clear();
4844       newStructureViewers = null;
4845     }
4846   }
4847
4848   AlignFrame loadViewport(String file, List<JSeq> JSEQ,
4849           List<SequenceI> hiddenSeqs, AlignmentI al,
4850           JalviewModel jm, Viewport view, String uniqueSeqSetId,
4851           String viewId, List<JvAnnotRow> autoAlan)
4852   {
4853     AlignFrame af = null;
4854     af = new AlignFrame(al, safeInt(view.getWidth()),
4855             safeInt(view.getHeight()), uniqueSeqSetId, viewId);
4856
4857     af.setFileName(file, FileFormat.Jalview);
4858
4859     final AlignViewport viewport = af.getViewport();
4860     for (int i = 0; i < JSEQ.size(); i++)
4861     {
4862       int colour = safeInt(JSEQ.get(i).getColour());
4863       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
4864               new Color(colour));
4865     }
4866
4867     if (al.hasSeqrep())
4868     {
4869       viewport.setColourByReferenceSeq(true);
4870       viewport.setDisplayReferenceSeq(true);
4871     }
4872
4873     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
4874
4875     if (view.getSequenceSetId() != null)
4876     {
4877       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4878
4879       viewport.setSequenceSetId(uniqueSeqSetId);
4880       if (av != null)
4881       {
4882         // propagate shared settings to this new view
4883         viewport.setHistoryList(av.getHistoryList());
4884         viewport.setRedoList(av.getRedoList());
4885       }
4886       else
4887       {
4888         viewportsAdded.put(uniqueSeqSetId, viewport);
4889       }
4890       // TODO: check if this method can be called repeatedly without
4891       // side-effects if alignpanel already registered.
4892       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4893     }
4894     // apply Hidden regions to view.
4895     if (hiddenSeqs != null)
4896     {
4897       for (int s = 0; s < JSEQ.size(); s++)
4898       {
4899         SequenceGroup hidden = new SequenceGroup();
4900         boolean isRepresentative = false;
4901         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
4902         {
4903           isRepresentative = true;
4904           SequenceI sequenceToHide = al
4905                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
4906           hidden.addSequence(sequenceToHide, false);
4907           // remove from hiddenSeqs list so we don't try to hide it twice
4908           hiddenSeqs.remove(sequenceToHide);
4909         }
4910         if (isRepresentative)
4911         {
4912           SequenceI representativeSequence = al.getSequenceAt(s);
4913           hidden.addSequence(representativeSequence, false);
4914           viewport.hideRepSequences(representativeSequence, hidden);
4915         }
4916       }
4917
4918       SequenceI[] hseqs = hiddenSeqs
4919               .toArray(new SequenceI[hiddenSeqs.size()]);
4920       viewport.hideSequence(hseqs);
4921
4922     }
4923     // recover view properties and display parameters
4924
4925     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4926     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
4927     final int pidThreshold = safeInt(view.getPidThreshold());
4928     viewport.setThreshold(pidThreshold);
4929
4930     viewport.setColourText(safeBoolean(view.isShowColourText()));
4931
4932     viewport
4933             .setConservationSelected(
4934                     safeBoolean(view.isConservationSelected()));
4935     viewport.setIncrement(safeInt(view.getConsThreshold()));
4936     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
4937     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
4938     viewport.setFont(new Font(view.getFontName(),
4939             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
4940             true);
4941     ViewStyleI vs = viewport.getViewStyle();
4942     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4943     viewport.setViewStyle(vs);
4944     // TODO: allow custom charWidth/Heights to be restored by updating them
4945     // after setting font - which means set above to false
4946     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
4947     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
4948     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4949
4950     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
4951
4952     viewport.setShowText(safeBoolean(view.isShowText()));
4953
4954     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
4955     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
4956     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
4957     viewport.setShowUnconserved(view.isShowUnconserved());
4958     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
4959
4960     if (view.getViewName() != null)
4961     {
4962       viewport.setViewName(view.getViewName());
4963       af.setInitialTabVisible();
4964     }
4965     af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
4966             safeInt(view.getWidth()), safeInt(view.getHeight()));
4967     // startSeq set in af.alignPanel.updateLayout below
4968     af.alignPanel.updateLayout();
4969     ColourSchemeI cs = null;
4970     // apply colourschemes
4971     if (view.getBgColour() != null)
4972     {
4973       if (view.getBgColour().startsWith("ucs"))
4974       {
4975         cs = getUserColourScheme(jm, view.getBgColour());
4976       }
4977       else if (view.getBgColour().startsWith("Annotation"))
4978       {
4979         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
4980         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
4981
4982         // annpos
4983
4984       }
4985       else
4986       {
4987         cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
4988                 view.getBgColour());
4989       }
4990     }
4991
4992     /*
4993      * turn off 'alignment colour applies to all groups'
4994      * while restoring global colour scheme
4995      */
4996     viewport.setColourAppliesToAllGroups(false);
4997     viewport.setGlobalColourScheme(cs);
4998     viewport.getResidueShading().setThreshold(pidThreshold,
4999             view.isIgnoreGapsinConsensus());
5000     viewport.getResidueShading()
5001             .setConsensus(viewport.getSequenceConsensusHash());
5002     if (safeBoolean(view.isConservationSelected()) && cs != null)
5003     {
5004       viewport.getResidueShading()
5005               .setConservationInc(safeInt(view.getConsThreshold()));
5006     }
5007     af.changeColour(cs);
5008     viewport.setColourAppliesToAllGroups(true);
5009
5010     viewport
5011             .setShowSequenceFeatures(
5012                     safeBoolean(view.isShowSequenceFeatures()));
5013
5014     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
5015     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
5016     viewport.setFollowHighlight(view.isFollowHighlight());
5017     viewport.followSelection = view.isFollowSelection();
5018     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
5019     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
5020     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
5021     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
5022     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
5023     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
5024     viewport.setShowGroupConservation(view.isShowGroupConservation());
5025     viewport.setShowComplementFeatures(view.isShowComplementFeatures());
5026     viewport.setShowComplementFeaturesOnTop(
5027             view.isShowComplementFeaturesOnTop());
5028
5029     // recover feature settings
5030     if (jm.getFeatureSettings() != null)
5031     {
5032       FeatureRendererModel fr = af.alignPanel.getSeqPanel().seqCanvas
5033               .getFeatureRenderer();
5034       FeaturesDisplayed fdi;
5035       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
5036       String[] renderOrder = new String[jm.getFeatureSettings()
5037               .getSetting().size()];
5038       Map<String, FeatureColourI> featureColours = new Hashtable<>();
5039       Map<String, Float> featureOrder = new Hashtable<>();
5040
5041       for (int fs = 0; fs < jm.getFeatureSettings()
5042               .getSetting().size(); fs++)
5043       {
5044         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
5045         String featureType = setting.getType();
5046
5047         /*
5048          * restore feature filters (if any)
5049          */
5050         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
5051                 .getMatcherSet();
5052         if (filters != null)
5053         {
5054           FeatureMatcherSetI filter = Jalview2XML
5055                   .parseFilter(featureType, filters);
5056           if (!filter.isEmpty())
5057           {
5058             fr.setFeatureFilter(featureType, filter);
5059           }
5060         }
5061
5062         /*
5063          * restore feature colour scheme
5064          */
5065         Color maxColour = new Color(setting.getColour());
5066         if (setting.getMincolour() != null)
5067         {
5068           /*
5069            * minColour is always set unless a simple colour
5070            * (including for colour by label though it doesn't use it)
5071            */
5072           Color minColour = new Color(setting.getMincolour().intValue());
5073           Color noValueColour = minColour;
5074           NoValueColour noColour = setting.getNoValueColour();
5075           if (noColour == NoValueColour.NONE)
5076           {
5077             noValueColour = null;
5078           }
5079           else if (noColour == NoValueColour.MAX)
5080           {
5081             noValueColour = maxColour;
5082           }
5083           float min = safeFloat(safeFloat(setting.getMin()));
5084           float max = setting.getMax() == null ? 1f
5085                   : setting.getMax().floatValue();
5086           FeatureColourI gc = new FeatureColour(maxColour, minColour,
5087                   maxColour,
5088                   noValueColour, min, max);
5089           if (setting.getAttributeName().size() > 0)
5090           {
5091             gc.setAttributeName(setting.getAttributeName().toArray(
5092                     new String[setting.getAttributeName().size()]));
5093           }
5094           if (setting.getThreshold() != null)
5095           {
5096             gc.setThreshold(setting.getThreshold().floatValue());
5097             int threshstate = safeInt(setting.getThreshstate());
5098             // -1 = None, 0 = Below, 1 = Above threshold
5099             if (threshstate == 0)
5100             {
5101               gc.setBelowThreshold(true);
5102             }
5103             else if (threshstate == 1)
5104             {
5105               gc.setAboveThreshold(true);
5106             }
5107           }
5108           gc.setAutoScaled(true); // default
5109           if (setting.isAutoScale() != null)
5110           {
5111             gc.setAutoScaled(setting.isAutoScale());
5112           }
5113           if (setting.isColourByLabel() != null)
5114           {
5115             gc.setColourByLabel(setting.isColourByLabel());
5116           }
5117           // and put in the feature colour table.
5118           featureColours.put(featureType, gc);
5119         }
5120         else
5121         {
5122           featureColours.put(featureType,
5123                   new FeatureColour(maxColour));
5124         }
5125         renderOrder[fs] = featureType;
5126         if (setting.getOrder() != null)
5127         {
5128           featureOrder.put(featureType, setting.getOrder().floatValue());
5129         }
5130         else
5131         {
5132           featureOrder.put(featureType, Float.valueOf(
5133                   fs / jm.getFeatureSettings().getSetting().size()));
5134         }
5135         if (safeBoolean(setting.isDisplay()))
5136         {
5137           fdi.setVisible(featureType);
5138         }
5139       }
5140       Map<String, Boolean> fgtable = new Hashtable<>();
5141       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
5142       {
5143         Group grp = jm.getFeatureSettings().getGroup().get(gs);
5144         fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay()));
5145       }
5146       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5147       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
5148       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
5149       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5150               fgtable, featureColours, 1.0f, featureOrder);
5151       fr.transferSettings(frs);
5152     }
5153
5154     if (view.getHiddenColumns().size() > 0)
5155     {
5156       for (int c = 0; c < view.getHiddenColumns().size(); c++)
5157       {
5158         final HiddenColumns hc = view.getHiddenColumns().get(c);
5159         viewport.hideColumns(safeInt(hc.getStart()),
5160                 safeInt(hc.getEnd()) /* +1 */);
5161       }
5162     }
5163     if (view.getCalcIdParam() != null)
5164     {
5165       for (CalcIdParam calcIdParam : view.getCalcIdParam())
5166       {
5167         if (calcIdParam != null)
5168         {
5169           if (recoverCalcIdParam(calcIdParam, viewport))
5170           {
5171           }
5172           else
5173           {
5174             warn("Couldn't recover parameters for "
5175                     + calcIdParam.getCalcId());
5176           }
5177         }
5178       }
5179     }
5180     af.setMenusFromViewport(viewport);
5181     af.setTitle(view.getTitle());
5182     // TODO: we don't need to do this if the viewport is aready visible.
5183     /*
5184      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
5185      * has a 'cdna/protein complement' view, in which case save it in order to
5186      * populate a SplitFrame once all views have been read in.
5187      */
5188     String complementaryViewId = view.getComplementId();
5189     if (complementaryViewId == null)
5190     {
5191       Desktop.addInternalFrame(af, view.getTitle(),
5192               safeInt(view.getWidth()), safeInt(view.getHeight()));
5193       // recompute any autoannotation
5194       af.alignPanel.updateAnnotation(false, true);
5195       reorderAutoannotation(af, al, autoAlan);
5196       af.alignPanel.alignmentChanged();
5197     }
5198     else
5199     {
5200       splitFrameCandidates.put(view, af);
5201     }
5202     return af;
5203   }
5204
5205   /**
5206    * Reads saved data to restore Colour by Annotation settings
5207    * 
5208    * @param viewAnnColour
5209    * @param af
5210    * @param al
5211    * @param model
5212    * @param checkGroupAnnColour
5213    * @return
5214    */
5215   private ColourSchemeI constructAnnotationColour(
5216           AnnotationColourScheme viewAnnColour, AlignFrame af,
5217           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
5218   {
5219     boolean propagateAnnColour = false;
5220     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
5221             : al;
5222     if (checkGroupAnnColour && al.getGroups() != null
5223             && al.getGroups().size() > 0)
5224     {
5225       // pre 2.8.1 behaviour
5226       // check to see if we should transfer annotation colours
5227       propagateAnnColour = true;
5228       for (SequenceGroup sg : al.getGroups())
5229       {
5230         if (sg.getColourScheme() instanceof AnnotationColourGradient)
5231         {
5232           propagateAnnColour = false;
5233         }
5234       }
5235     }
5236
5237     /*
5238      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
5239      */
5240     String annotationId = viewAnnColour.getAnnotation();
5241     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
5242
5243     /*
5244      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
5245      */
5246     if (matchedAnnotation == null
5247             && annAlignment.getAlignmentAnnotation() != null)
5248     {
5249       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
5250       {
5251         if (annotationId
5252                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
5253         {
5254           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
5255           break;
5256         }
5257       }
5258     }
5259     if (matchedAnnotation == null)
5260     {
5261       System.err.println("Failed to match annotation colour scheme for "
5262               + annotationId);
5263       return null;
5264     }
5265     if (matchedAnnotation.getThreshold() == null)
5266     {
5267       matchedAnnotation.setThreshold(
5268               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5269                       "Threshold", Color.black));
5270     }
5271
5272     AnnotationColourGradient cs = null;
5273     if (viewAnnColour.getColourScheme().equals("None"))
5274     {
5275       cs = new AnnotationColourGradient(matchedAnnotation,
5276               new Color(safeInt(viewAnnColour.getMinColour())),
5277               new Color(safeInt(viewAnnColour.getMaxColour())),
5278               safeInt(viewAnnColour.getAboveThreshold()));
5279     }
5280     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5281     {
5282       cs = new AnnotationColourGradient(matchedAnnotation,
5283               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5284               safeInt(viewAnnColour.getAboveThreshold()));
5285     }
5286     else
5287     {
5288       cs = new AnnotationColourGradient(matchedAnnotation,
5289               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5290                       viewAnnColour.getColourScheme()),
5291               safeInt(viewAnnColour.getAboveThreshold()));
5292     }
5293
5294     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5295     boolean useOriginalColours = safeBoolean(
5296             viewAnnColour.isPredefinedColours());
5297     cs.setSeqAssociated(perSequenceOnly);
5298     cs.setPredefinedColours(useOriginalColours);
5299
5300     if (propagateAnnColour && al.getGroups() != null)
5301     {
5302       // Also use these settings for all the groups
5303       for (int g = 0; g < al.getGroups().size(); g++)
5304       {
5305         SequenceGroup sg = al.getGroups().get(g);
5306         if (sg.getGroupColourScheme() == null)
5307         {
5308           continue;
5309         }
5310
5311         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5312                 matchedAnnotation, sg.getColourScheme(),
5313                 safeInt(viewAnnColour.getAboveThreshold()));
5314         sg.setColourScheme(groupScheme);
5315         groupScheme.setSeqAssociated(perSequenceOnly);
5316         groupScheme.setPredefinedColours(useOriginalColours);
5317       }
5318     }
5319     return cs;
5320   }
5321
5322   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5323           List<JvAnnotRow> autoAlan)
5324   {
5325     // copy over visualization settings for autocalculated annotation in the
5326     // view
5327     if (al.getAlignmentAnnotation() != null)
5328     {
5329       /**
5330        * Kludge for magic autoannotation names (see JAL-811)
5331        */
5332       String[] magicNames = new String[] { "Consensus", "Quality",
5333           "Conservation" };
5334       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5335       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5336       for (String nm : magicNames)
5337       {
5338         visan.put(nm, nullAnnot);
5339       }
5340       for (JvAnnotRow auan : autoAlan)
5341       {
5342         visan.put(auan.template.label
5343                 + (auan.template.getCalcId() == null ? ""
5344                         : "\t" + auan.template.getCalcId()),
5345                 auan);
5346       }
5347       int hSize = al.getAlignmentAnnotation().length;
5348       List<JvAnnotRow> reorder = new ArrayList<>();
5349       // work through any autoCalculated annotation already on the view
5350       // removing it if it should be placed in a different location on the
5351       // annotation panel.
5352       List<String> remains = new ArrayList<>(visan.keySet());
5353       for (int h = 0; h < hSize; h++)
5354       {
5355         jalview.datamodel.AlignmentAnnotation jalan = al
5356                 .getAlignmentAnnotation()[h];
5357         if (jalan.autoCalculated)
5358         {
5359           String k;
5360           JvAnnotRow valan = visan.get(k = jalan.label);
5361           if (jalan.getCalcId() != null)
5362           {
5363             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5364           }
5365
5366           if (valan != null)
5367           {
5368             // delete the auto calculated row from the alignment
5369             al.deleteAnnotation(jalan, false);
5370             remains.remove(k);
5371             hSize--;
5372             h--;
5373             if (valan != nullAnnot)
5374             {
5375               if (jalan != valan.template)
5376               {
5377                 // newly created autoannotation row instance
5378                 // so keep a reference to the visible annotation row
5379                 // and copy over all relevant attributes
5380                 if (valan.template.graphHeight >= 0)
5381
5382                 {
5383                   jalan.graphHeight = valan.template.graphHeight;
5384                 }
5385                 jalan.visible = valan.template.visible;
5386               }
5387               reorder.add(new JvAnnotRow(valan.order, jalan));
5388             }
5389           }
5390         }
5391       }
5392       // Add any (possibly stale) autocalculated rows that were not appended to
5393       // the view during construction
5394       for (String other : remains)
5395       {
5396         JvAnnotRow othera = visan.get(other);
5397         if (othera != nullAnnot && othera.template.getCalcId() != null
5398                 && othera.template.getCalcId().length() > 0)
5399         {
5400           reorder.add(othera);
5401         }
5402       }
5403       // now put the automatic annotation in its correct place
5404       int s = 0, srt[] = new int[reorder.size()];
5405       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5406       for (JvAnnotRow jvar : reorder)
5407       {
5408         rws[s] = jvar;
5409         srt[s++] = jvar.order;
5410       }
5411       reorder.clear();
5412       jalview.util.QuickSort.sort(srt, rws);
5413       // and re-insert the annotation at its correct position
5414       for (JvAnnotRow jvar : rws)
5415       {
5416         al.addAnnotation(jvar.template, jvar.order);
5417       }
5418       af.alignPanel.adjustAnnotationHeight();
5419     }
5420   }
5421
5422   Hashtable skipList = null;
5423
5424   /**
5425    * TODO remove this method
5426    * 
5427    * @param view
5428    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5429    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5430    *         throw new Error("Implementation Error. No skipList defined for this
5431    *         Jalview2XML instance."); } return (AlignFrame)
5432    *         skipList.get(view.getSequenceSetId()); }
5433    */
5434
5435   /**
5436    * Check if the Jalview view contained in object should be skipped or not.
5437    * 
5438    * @param object
5439    * @return true if view's sequenceSetId is a key in skipList
5440    */
5441   private boolean skipViewport(JalviewModel object)
5442   {
5443     if (skipList == null)
5444     {
5445       return false;
5446     }
5447     String id = object.getViewport().get(0).getSequenceSetId();
5448     if (skipList.containsKey(id))
5449     {
5450       if (Cache.log != null && Cache.log.isDebugEnabled())
5451       {
5452         Cache.log.debug("Skipping seuqence set id " + id);
5453       }
5454       return true;
5455     }
5456     return false;
5457   }
5458
5459   public void addToSkipList(AlignFrame af)
5460   {
5461     if (skipList == null)
5462     {
5463       skipList = new Hashtable();
5464     }
5465     skipList.put(af.getViewport().getSequenceSetId(), af);
5466   }
5467
5468   public void clearSkipList()
5469   {
5470     if (skipList != null)
5471     {
5472       skipList.clear();
5473       skipList = null;
5474     }
5475   }
5476
5477   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5478           boolean ignoreUnrefed, String uniqueSeqSetId)
5479   {
5480     jalview.datamodel.AlignmentI ds = getDatasetFor(
5481             vamsasSet.getDatasetId());
5482     AlignmentI xtant_ds = ds;
5483     if (xtant_ds == null)
5484     {
5485       // good chance we are about to create a new dataset, but check if we've
5486       // seen some of the dataset sequence IDs before.
5487       // TODO: skip this check if we are working with project generated by
5488       // version 2.11 or later
5489       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5490       if (xtant_ds != null)
5491       {
5492         ds = xtant_ds;
5493         addDatasetRef(vamsasSet.getDatasetId(), ds);
5494       }
5495     }
5496     Vector dseqs = null;
5497     if (!ignoreUnrefed)
5498     {
5499       // recovering an alignment View
5500       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5501       if (seqSetDS != null)
5502       {
5503         if (ds != null && ds != seqSetDS)
5504         {
5505           warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
5506                   + " - CDS/Protein crossreference data may be lost");
5507           if (xtant_ds != null)
5508           {
5509             // This can only happen if the unique sequence set ID was bound to a
5510             // dataset that did not contain any of the sequences in the view
5511             // currently being restored.
5512             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.");
5513           }
5514         }
5515         ds = seqSetDS;
5516         addDatasetRef(vamsasSet.getDatasetId(), ds);
5517       }
5518     }
5519     if (ds == null)
5520     {
5521       // try even harder to restore dataset
5522       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5523       // create a list of new dataset sequences
5524       dseqs = new Vector();
5525     }
5526     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5527     {
5528       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5529       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5530     }
5531     // create a new dataset
5532     if (ds == null)
5533     {
5534       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5535       dseqs.copyInto(dsseqs);
5536       ds = new jalview.datamodel.Alignment(dsseqs);
5537       debug("Created new dataset " + vamsasSet.getDatasetId()
5538               + " for alignment " + System.identityHashCode(al));
5539       addDatasetRef(vamsasSet.getDatasetId(), ds);
5540     }
5541     // set the dataset for the newly imported alignment.
5542     if (al.getDataset() == null && !ignoreUnrefed)
5543     {
5544       al.setDataset(ds);
5545       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5546       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5547     }
5548     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5549   }
5550
5551   /**
5552    * XML dataset sequence ID to materialised dataset reference
5553    */
5554   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5555
5556   /**
5557    * @return the first materialised dataset reference containing a dataset
5558    *         sequence referenced in the given view
5559    * @param list
5560    *          - sequences from the view
5561    */
5562   AlignmentI checkIfHasDataset(List<Sequence> list)
5563   {
5564     for (Sequence restoredSeq : list)
5565     {
5566       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5567       if (datasetFor != null)
5568       {
5569         return datasetFor;
5570       }
5571     }
5572     return null;
5573   }
5574
5575   /**
5576    * Register ds as the containing dataset for the dataset sequences referenced
5577    * by sequences in list
5578    * 
5579    * @param list
5580    *          - sequences in a view
5581    * @param ds
5582    */
5583   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
5584   {
5585     for (Sequence restoredSeq : list)
5586     {
5587       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
5588       if (prevDS != null && prevDS != ds)
5589       {
5590         warn("Dataset sequence appears in many datasets: "
5591                 + restoredSeq.getDsseqid());
5592         // TODO: try to merge!
5593       }
5594     }
5595   }
5596   /**
5597    * 
5598    * @param vamsasSeq
5599    *          sequence definition to create/merge dataset sequence for
5600    * @param ds
5601    *          dataset alignment
5602    * @param dseqs
5603    *          vector to add new dataset sequence to
5604    * @param ignoreUnrefed
5605    *          - when true, don't create new sequences from vamsasSeq if it's id
5606    *          doesn't already have an asssociated Jalview sequence.
5607    * @param vseqpos
5608    *          - used to reorder the sequence in the alignment according to the
5609    *          vamsasSeq array ordering, to preserve ordering of dataset
5610    */
5611   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5612           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5613   {
5614     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5615     // xRef Codon Maps
5616     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5617     boolean reorder = false;
5618     SequenceI dsq = null;
5619     if (sq != null && sq.getDatasetSequence() != null)
5620     {
5621       dsq = sq.getDatasetSequence();
5622     }
5623     else
5624     {
5625       reorder = true;
5626     }
5627     if (sq == null && ignoreUnrefed)
5628     {
5629       return;
5630     }
5631     String sqid = vamsasSeq.getDsseqid();
5632     if (dsq == null)
5633     {
5634       // need to create or add a new dataset sequence reference to this sequence
5635       if (sqid != null)
5636       {
5637         dsq = seqRefIds.get(sqid);
5638       }
5639       // check again
5640       if (dsq == null)
5641       {
5642         // make a new dataset sequence
5643         dsq = sq.createDatasetSequence();
5644         if (sqid == null)
5645         {
5646           // make up a new dataset reference for this sequence
5647           sqid = seqHash(dsq);
5648         }
5649         dsq.setVamsasId(uniqueSetSuffix + sqid);
5650         seqRefIds.put(sqid, dsq);
5651         if (ds == null)
5652         {
5653           if (dseqs != null)
5654           {
5655             dseqs.addElement(dsq);
5656           }
5657         }
5658         else
5659         {
5660           ds.addSequence(dsq);
5661         }
5662       }
5663       else
5664       {
5665         if (sq != dsq)
5666         { // make this dataset sequence sq's dataset sequence
5667           sq.setDatasetSequence(dsq);
5668           // and update the current dataset alignment
5669           if (ds == null)
5670           {
5671             if (dseqs != null)
5672             {
5673               if (!dseqs.contains(dsq))
5674               {
5675                 dseqs.add(dsq);
5676               }
5677             }
5678             else
5679             {
5680               if (ds.findIndex(dsq) < 0)
5681               {
5682                 ds.addSequence(dsq);
5683               }
5684             }
5685           }
5686         }
5687       }
5688     }
5689     // TODO: refactor this as a merge dataset sequence function
5690     // now check that sq (the dataset sequence) sequence really is the union of
5691     // all references to it
5692     // boolean pre = sq.getStart() < dsq.getStart();
5693     // boolean post = sq.getEnd() > dsq.getEnd();
5694     // if (pre || post)
5695     if (sq != dsq)
5696     {
5697       // StringBuffer sb = new StringBuffer();
5698       String newres = jalview.analysis.AlignSeq.extractGaps(
5699               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5700       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5701               && newres.length() > dsq.getLength())
5702       {
5703         // Update with the longer sequence.
5704         synchronized (dsq)
5705         {
5706           /*
5707            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5708            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5709            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5710            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5711            */
5712           dsq.setSequence(newres);
5713         }
5714         // TODO: merges will never happen if we 'know' we have the real dataset
5715         // sequence - this should be detected when id==dssid
5716         System.err.println(
5717                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5718         // + (pre ? "prepended" : "") + " "
5719         // + (post ? "appended" : ""));
5720       }
5721     }
5722     else
5723     {
5724       // sequence refs are identical. We may need to update the existing dataset
5725       // alignment with this one, though.
5726       if (ds != null && dseqs == null)
5727       {
5728         int opos = ds.findIndex(dsq);
5729         SequenceI tseq = null;
5730         if (opos != -1 && vseqpos != opos)
5731         {
5732           // remove from old position
5733           ds.deleteSequence(dsq);
5734         }
5735         if (vseqpos < ds.getHeight())
5736         {
5737           if (vseqpos != opos)
5738           {
5739             // save sequence at destination position
5740             tseq = ds.getSequenceAt(vseqpos);
5741             ds.replaceSequenceAt(vseqpos, dsq);
5742             ds.addSequence(tseq);
5743           }
5744         }
5745         else
5746         {
5747           ds.addSequence(dsq);
5748         }
5749       }
5750     }
5751   }
5752
5753   /*
5754    * TODO use AlignmentI here and in related methods - needs
5755    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5756    */
5757   Hashtable<String, AlignmentI> datasetIds = null;
5758
5759   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5760
5761   private AlignmentI getDatasetFor(String datasetId)
5762   {
5763     if (datasetIds == null)
5764     {
5765       datasetIds = new Hashtable<>();
5766       return null;
5767     }
5768     if (datasetIds.containsKey(datasetId))
5769     {
5770       return datasetIds.get(datasetId);
5771     }
5772     return null;
5773   }
5774
5775   private void addDatasetRef(String datasetId, AlignmentI dataset)
5776   {
5777     if (datasetIds == null)
5778     {
5779       datasetIds = new Hashtable<>();
5780     }
5781     datasetIds.put(datasetId, dataset);
5782   }
5783
5784   /**
5785    * make a new dataset ID for this jalview dataset alignment
5786    * 
5787    * @param dataset
5788    * @return
5789    */
5790   private String getDatasetIdRef(AlignmentI dataset)
5791   {
5792     if (dataset.getDataset() != null)
5793     {
5794       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5795     }
5796     String datasetId = makeHashCode(dataset, null);
5797     if (datasetId == null)
5798     {
5799       // make a new datasetId and record it
5800       if (dataset2Ids == null)
5801       {
5802         dataset2Ids = new IdentityHashMap<>();
5803       }
5804       else
5805       {
5806         datasetId = dataset2Ids.get(dataset);
5807       }
5808       if (datasetId == null)
5809       {
5810         datasetId = "ds" + dataset2Ids.size() + 1;
5811         dataset2Ids.put(dataset, datasetId);
5812       }
5813     }
5814     return datasetId;
5815   }
5816
5817   /**
5818    * Add any saved DBRefEntry's to the sequence. An entry flagged as 'locus' is
5819    * constructed as a special subclass GeneLocus.
5820    * 
5821    * @param datasetSequence
5822    * @param sequence
5823    */
5824   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5825   {
5826     for (int d = 0; d < sequence.getDBRef().size(); d++)
5827     {
5828       DBRef dr = sequence.getDBRef().get(d);
5829       DBRefEntry entry;
5830       if (dr.isLocus())
5831       {
5832         entry = new GeneLocus(dr.getSource(), dr.getVersion(),
5833                 dr.getAccessionId());
5834       }
5835       else
5836       {
5837         entry = new DBRefEntry(dr.getSource(), dr.getVersion(),
5838                 dr.getAccessionId());
5839       }
5840       if (dr.getMapping() != null)
5841       {
5842         entry.setMap(addMapping(dr.getMapping()));
5843       }
5844       datasetSequence.addDBRef(entry);
5845     }
5846   }
5847
5848   private jalview.datamodel.Mapping addMapping(Mapping m)
5849   {
5850     SequenceI dsto = null;
5851     // Mapping m = dr.getMapping();
5852     int fr[] = new int[m.getMapListFrom().size() * 2];
5853     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
5854     for (int _i = 0; from.hasNext(); _i += 2)
5855     {
5856       MapListFrom mf = from.next();
5857       fr[_i] = mf.getStart();
5858       fr[_i + 1] = mf.getEnd();
5859     }
5860     int fto[] = new int[m.getMapListTo().size() * 2];
5861     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
5862     for (int _i = 0; to.hasNext(); _i += 2)
5863     {
5864       MapListTo mf = to.next();
5865       fto[_i] = mf.getStart();
5866       fto[_i + 1] = mf.getEnd();
5867     }
5868     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5869             fto, m.getMapFromUnit().intValue(),
5870             m.getMapToUnit().intValue());
5871
5872     /*
5873      * (optional) choice of dseqFor or Sequence
5874      */
5875     if (m.getDseqFor() != null)
5876     {
5877       String dsfor = m.getDseqFor();
5878       if (seqRefIds.containsKey(dsfor))
5879       {
5880         /*
5881          * recover from hash
5882          */
5883         jmap.setTo(seqRefIds.get(dsfor));
5884       }
5885       else
5886       {
5887         frefedSequence.add(newMappingRef(dsfor, jmap));
5888       }
5889     }
5890     else if (m.getSequence() != null)
5891     {
5892       /*
5893        * local sequence definition
5894        */
5895       Sequence ms = m.getSequence();
5896       SequenceI djs = null;
5897       String sqid = ms.getDsseqid();
5898       if (sqid != null && sqid.length() > 0)
5899       {
5900         /*
5901          * recover dataset sequence
5902          */
5903         djs = seqRefIds.get(sqid);
5904       }
5905       else
5906       {
5907         System.err.println(
5908                 "Warning - making up dataset sequence id for DbRef sequence map reference");
5909         sqid = ((Object) ms).toString(); // make up a new hascode for
5910         // undefined dataset sequence hash
5911         // (unlikely to happen)
5912       }
5913
5914       if (djs == null)
5915       {
5916         /**
5917          * make a new dataset sequence and add it to refIds hash
5918          */
5919         djs = new jalview.datamodel.Sequence(ms.getName(),
5920                 ms.getSequence());
5921         djs.setStart(jmap.getMap().getToLowest());
5922         djs.setEnd(jmap.getMap().getToHighest());
5923         djs.setVamsasId(uniqueSetSuffix + sqid);
5924         jmap.setTo(djs);
5925         incompleteSeqs.put(sqid, djs);
5926         seqRefIds.put(sqid, djs);
5927
5928       }
5929       jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5930       addDBRefs(djs, ms);
5931
5932     }
5933
5934     return jmap;
5935   }
5936
5937   /**
5938    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
5939    * view as XML (but not to file), and then reloading it
5940    * 
5941    * @param ap
5942    * @return
5943    */
5944   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
5945   {
5946     initSeqRefs();
5947     JalviewModel jm = saveState(ap, null, null, null);
5948
5949     addDatasetRef(
5950             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
5951             ap.getAlignment().getDataset());
5952
5953     uniqueSetSuffix = "";
5954     // jm.getJalviewModelSequence().getViewport(0).setId(null);
5955     jm.getViewport().get(0).setId(null);
5956     // we don't overwrite the view we just copied
5957
5958     if (this.frefedSequence == null)
5959     {
5960       frefedSequence = new Vector<>();
5961     }
5962
5963     viewportsAdded.clear();
5964
5965     AlignFrame af = loadFromObject(jm, null, false, null);
5966     af.getAlignPanels().clear();
5967     af.closeMenuItem_actionPerformed(true);
5968
5969     /*
5970      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5971      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5972      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5973      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5974      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5975      */
5976
5977     return af.alignPanel;
5978   }
5979
5980   private Hashtable jvids2vobj;
5981
5982   private void warn(String msg)
5983   {
5984     warn(msg, null);
5985   }
5986
5987   private void warn(String msg, Exception e)
5988   {
5989     if (Cache.log != null)
5990     {
5991       if (e != null)
5992       {
5993         Cache.log.warn(msg, e);
5994       }
5995       else
5996       {
5997         Cache.log.warn(msg);
5998       }
5999     }
6000     else
6001     {
6002       System.err.println("Warning: " + msg);
6003       if (e != null)
6004       {
6005         e.printStackTrace();
6006       }
6007     }
6008   }
6009
6010   private void debug(String string)
6011   {
6012     debug(string, null);
6013   }
6014
6015   private void debug(String msg, Exception e)
6016   {
6017     if (Cache.log != null)
6018     {
6019       if (e != null)
6020       {
6021         Cache.log.debug(msg, e);
6022       }
6023       else
6024       {
6025         Cache.log.debug(msg);
6026       }
6027     }
6028     else
6029     {
6030       System.err.println("Warning: " + msg);
6031       if (e != null)
6032       {
6033         e.printStackTrace();
6034       }
6035     }
6036   }
6037
6038   /**
6039    * set the object to ID mapping tables used to write/recover objects and XML
6040    * ID strings for the jalview project. If external tables are provided then
6041    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
6042    * object goes out of scope. - also populates the datasetIds hashtable with
6043    * alignment objects containing dataset sequences
6044    * 
6045    * @param vobj2jv
6046    *          Map from ID strings to jalview datamodel
6047    * @param jv2vobj
6048    *          Map from jalview datamodel to ID strings
6049    * 
6050    * 
6051    */
6052   public void setObjectMappingTables(Hashtable vobj2jv,
6053           IdentityHashMap jv2vobj)
6054   {
6055     this.jv2vobj = jv2vobj;
6056     this.vobj2jv = vobj2jv;
6057     Iterator ds = jv2vobj.keySet().iterator();
6058     String id;
6059     while (ds.hasNext())
6060     {
6061       Object jvobj = ds.next();
6062       id = jv2vobj.get(jvobj).toString();
6063       if (jvobj instanceof jalview.datamodel.Alignment)
6064       {
6065         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
6066         {
6067           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
6068         }
6069       }
6070       else if (jvobj instanceof jalview.datamodel.Sequence)
6071       {
6072         // register sequence object so the XML parser can recover it.
6073         if (seqRefIds == null)
6074         {
6075           seqRefIds = new HashMap<>();
6076         }
6077         if (seqsToIds == null)
6078         {
6079           seqsToIds = new IdentityHashMap<>();
6080         }
6081         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
6082         seqsToIds.put((SequenceI) jvobj, id);
6083       }
6084       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
6085       {
6086         String anid;
6087         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
6088         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
6089         if (jvann.annotationId == null)
6090         {
6091           jvann.annotationId = anid;
6092         }
6093         if (!jvann.annotationId.equals(anid))
6094         {
6095           // TODO verify that this is the correct behaviour
6096           this.warn("Overriding Annotation ID for " + anid
6097                   + " from different id : " + jvann.annotationId);
6098           jvann.annotationId = anid;
6099         }
6100       }
6101       else if (jvobj instanceof String)
6102       {
6103         if (jvids2vobj == null)
6104         {
6105           jvids2vobj = new Hashtable();
6106           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
6107         }
6108       }
6109       else
6110       {
6111         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
6112       }
6113     }
6114   }
6115
6116   /**
6117    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
6118    * objects created from the project archive. If string is null (default for
6119    * construction) then suffix will be set automatically.
6120    * 
6121    * @param string
6122    */
6123   public void setUniqueSetSuffix(String string)
6124   {
6125     uniqueSetSuffix = string;
6126
6127   }
6128
6129   /**
6130    * uses skipList2 as the skipList for skipping views on sequence sets
6131    * associated with keys in the skipList
6132    * 
6133    * @param skipList2
6134    */
6135   public void setSkipList(Hashtable skipList2)
6136   {
6137     skipList = skipList2;
6138   }
6139
6140   /**
6141    * Reads the jar entry of given name and returns its contents, or null if the
6142    * entry is not found.
6143    * 
6144    * @param jprovider
6145    * @param jarEntryName
6146    * @return
6147    */
6148   protected String readJarEntry(jarInputStreamProvider jprovider,
6149           String jarEntryName)
6150   {
6151     String result = null;
6152     BufferedReader in = null;
6153
6154     try
6155     {
6156       /*
6157        * Reopen the jar input stream and traverse its entries to find a matching
6158        * name
6159        */
6160       JarInputStream jin = jprovider.getJarInputStream();
6161       JarEntry entry = null;
6162       do
6163       {
6164         entry = jin.getNextJarEntry();
6165       } while (entry != null && !entry.getName().equals(jarEntryName));
6166
6167       if (entry != null)
6168       {
6169         StringBuilder out = new StringBuilder(256);
6170         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
6171         String data;
6172
6173         while ((data = in.readLine()) != null)
6174         {
6175           out.append(data);
6176         }
6177         result = out.toString();
6178       }
6179       else
6180       {
6181         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
6182       }
6183     } catch (Exception ex)
6184     {
6185       ex.printStackTrace();
6186     } finally
6187     {
6188       if (in != null)
6189       {
6190         try
6191         {
6192           in.close();
6193         } catch (IOException e)
6194         {
6195           // ignore
6196         }
6197       }
6198     }
6199
6200     return result;
6201   }
6202
6203   /**
6204    * Returns an incrementing counter (0, 1, 2...)
6205    * 
6206    * @return
6207    */
6208   private synchronized int nextCounter()
6209   {
6210     return counter++;
6211   }
6212
6213   /**
6214    * Loads any saved PCA viewers
6215    * 
6216    * @param jms
6217    * @param ap
6218    */
6219   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
6220   {
6221     try
6222     {
6223       List<PcaViewer> pcaviewers = model.getPcaViewer();
6224       for (PcaViewer viewer : pcaviewers)
6225       {
6226         String modelName = viewer.getScoreModelName();
6227         SimilarityParamsI params = new SimilarityParams(
6228                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6229                 viewer.isIncludeGaps(),
6230                 viewer.isDenominateByShortestLength());
6231
6232         /*
6233          * create the panel (without computing the PCA)
6234          */
6235         PCAPanel panel = new PCAPanel(ap, modelName, params);
6236
6237         panel.setTitle(viewer.getTitle());
6238         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6239                 viewer.getWidth(), viewer.getHeight()));
6240
6241         boolean showLabels = viewer.isShowLabels();
6242         panel.setShowLabels(showLabels);
6243         panel.getRotatableCanvas().setShowLabels(showLabels);
6244         panel.getRotatableCanvas()
6245                 .setBgColour(new Color(viewer.getBgColour()));
6246         panel.getRotatableCanvas()
6247                 .setApplyToAllViews(viewer.isLinkToAllViews());
6248
6249         /*
6250          * load PCA output data
6251          */
6252         ScoreModelI scoreModel = ScoreModels.getInstance()
6253                 .getScoreModel(modelName, ap);
6254         PCA pca = new PCA(null, scoreModel, params);
6255         PcaDataType pcaData = viewer.getPcaData();
6256
6257         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
6258         pca.setPairwiseScores(pairwise);
6259
6260         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
6261         pca.setTridiagonal(triDiag);
6262
6263         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
6264         pca.setEigenmatrix(result);
6265
6266         panel.getPcaModel().setPCA(pca);
6267
6268         /*
6269          * we haven't saved the input data! (JAL-2647 to do)
6270          */
6271         panel.setInputData(null);
6272
6273         /*
6274          * add the sequence points for the PCA display
6275          */
6276         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6277         for (SequencePoint sp : viewer.getSequencePoint())
6278         {
6279           String seqId = sp.getSequenceRef();
6280           SequenceI seq = seqRefIds.get(seqId);
6281           if (seq == null)
6282           {
6283             throw new IllegalStateException(
6284                     "Unmatched seqref for PCA: " + seqId);
6285           }
6286           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6287           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6288                   seq, pt);
6289           seqPoints.add(seqPoint);
6290         }
6291         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6292
6293         /*
6294          * set min-max ranges and scale after setPoints (which recomputes them)
6295          */
6296         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6297         SeqPointMin spMin = viewer.getSeqPointMin();
6298         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6299             spMin.getZPos() };
6300         SeqPointMax spMax = viewer.getSeqPointMax();
6301         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6302             spMax.getZPos() };
6303         panel.getRotatableCanvas().setSeqMinMax(min, max);
6304
6305         // todo: hold points list in PCAModel only
6306         panel.getPcaModel().setSequencePoints(seqPoints);
6307
6308         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6309         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6310         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6311
6312         // is this duplication needed?
6313         panel.setTop(seqPoints.size() - 1);
6314         panel.getPcaModel().setTop(seqPoints.size() - 1);
6315
6316         /*
6317          * add the axes' end points for the display
6318          */
6319         for (int i = 0; i < 3; i++)
6320         {
6321           Axis axis = viewer.getAxis().get(i);
6322           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6323                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6324         }
6325
6326         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6327                 "label.calc_title", "PCA", modelName), 475, 450);
6328       }
6329     } catch (Exception ex)
6330     {
6331       Cache.log.error("Error loading PCA: " + ex.toString());
6332     }
6333   }
6334
6335   /**
6336    * Populates an XML model of the feature colour scheme for one feature type
6337    * 
6338    * @param featureType
6339    * @param fcol
6340    * @return
6341    */
6342   public static Colour marshalColour(
6343           String featureType, FeatureColourI fcol)
6344   {
6345     Colour col = new Colour();
6346     if (fcol.isSimpleColour())
6347     {
6348       col.setRGB(Format.getHexString(fcol.getColour()));
6349     }
6350     else
6351     {
6352       col.setRGB(Format.getHexString(fcol.getMaxColour()));
6353       col.setMin(fcol.getMin());
6354       col.setMax(fcol.getMax());
6355       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
6356       col.setAutoScale(fcol.isAutoScaled());
6357       col.setThreshold(fcol.getThreshold());
6358       col.setColourByLabel(fcol.isColourByLabel());
6359       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
6360               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
6361                       : ThresholdType.NONE));
6362       if (fcol.isColourByAttribute())
6363       {
6364         final String[] attName = fcol.getAttributeName();
6365         col.getAttributeName().add(attName[0]);
6366         if (attName.length > 1)
6367         {
6368           col.getAttributeName().add(attName[1]);
6369         }
6370       }
6371       Color noColour = fcol.getNoColour();
6372       if (noColour == null)
6373       {
6374         col.setNoValueColour(NoValueColour.NONE);
6375       }
6376       else if (noColour == fcol.getMaxColour())
6377       {
6378         col.setNoValueColour(NoValueColour.MAX);
6379       }
6380       else
6381       {
6382         col.setNoValueColour(NoValueColour.MIN);
6383       }
6384     }
6385     col.setName(featureType);
6386     return col;
6387   }
6388
6389   /**
6390    * Populates an XML model of the feature filter(s) for one feature type
6391    * 
6392    * @param firstMatcher
6393    *          the first (or only) match condition)
6394    * @param filter
6395    *          remaining match conditions (if any)
6396    * @param and
6397    *          if true, conditions are and-ed, else or-ed
6398    */
6399   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
6400           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
6401           boolean and)
6402   {
6403     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
6404   
6405     if (filters.hasNext())
6406     {
6407       /*
6408        * compound matcher
6409        */
6410       CompoundMatcher compound = new CompoundMatcher();
6411       compound.setAnd(and);
6412       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
6413               firstMatcher, Collections.emptyIterator(), and);
6414       // compound.addMatcherSet(matcher1);
6415       compound.getMatcherSet().add(matcher1);
6416       FeatureMatcherI nextMatcher = filters.next();
6417       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
6418               nextMatcher, filters, and);
6419       // compound.addMatcherSet(matcher2);
6420       compound.getMatcherSet().add(matcher2);
6421       result.setCompoundMatcher(compound);
6422     }
6423     else
6424     {
6425       /*
6426        * single condition matcher
6427        */
6428       // MatchCondition matcherModel = new MatchCondition();
6429       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
6430       matcherModel.setCondition(
6431               firstMatcher.getMatcher().getCondition().getStableName());
6432       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
6433       if (firstMatcher.isByAttribute())
6434       {
6435         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
6436         // matcherModel.setAttributeName(firstMatcher.getAttribute());
6437         String[] attName = firstMatcher.getAttribute();
6438         matcherModel.getAttributeName().add(attName[0]); // attribute
6439         if (attName.length > 1)
6440         {
6441           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
6442         }
6443       }
6444       else if (firstMatcher.isByLabel())
6445       {
6446         matcherModel.setBy(FilterBy.BY_LABEL);
6447       }
6448       else if (firstMatcher.isByScore())
6449       {
6450         matcherModel.setBy(FilterBy.BY_SCORE);
6451       }
6452       result.setMatchCondition(matcherModel);
6453     }
6454   
6455     return result;
6456   }
6457
6458   /**
6459    * Loads one XML model of a feature filter to a Jalview object
6460    * 
6461    * @param featureType
6462    * @param matcherSetModel
6463    * @return
6464    */
6465   public static FeatureMatcherSetI parseFilter(
6466           String featureType,
6467           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
6468   {
6469     FeatureMatcherSetI result = new FeatureMatcherSet();
6470     try
6471     {
6472       parseFilterConditions(result, matcherSetModel, true);
6473     } catch (IllegalStateException e)
6474     {
6475       // mixing AND and OR conditions perhaps
6476       System.err.println(
6477               String.format("Error reading filter conditions for '%s': %s",
6478                       featureType, e.getMessage()));
6479       // return as much as was parsed up to the error
6480     }
6481   
6482     return result;
6483   }
6484
6485   /**
6486    * Adds feature match conditions to matcherSet as unmarshalled from XML
6487    * (possibly recursively for compound conditions)
6488    * 
6489    * @param matcherSet
6490    * @param matcherSetModel
6491    * @param and
6492    *          if true, multiple conditions are AND-ed, else they are OR-ed
6493    * @throws IllegalStateException
6494    *           if AND and OR conditions are mixed
6495    */
6496   protected static void parseFilterConditions(
6497           FeatureMatcherSetI matcherSet,
6498           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
6499           boolean and)
6500   {
6501     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
6502             .getMatchCondition();
6503     if (mc != null)
6504     {
6505       /*
6506        * single condition
6507        */
6508       FilterBy filterBy = mc.getBy();
6509       Condition cond = Condition.fromString(mc.getCondition());
6510       String pattern = mc.getValue();
6511       FeatureMatcherI matchCondition = null;
6512       if (filterBy == FilterBy.BY_LABEL)
6513       {
6514         matchCondition = FeatureMatcher.byLabel(cond, pattern);
6515       }
6516       else if (filterBy == FilterBy.BY_SCORE)
6517       {
6518         matchCondition = FeatureMatcher.byScore(cond, pattern);
6519   
6520       }
6521       else if (filterBy == FilterBy.BY_ATTRIBUTE)
6522       {
6523         final List<String> attributeName = mc.getAttributeName();
6524         String[] attNames = attributeName
6525                 .toArray(new String[attributeName.size()]);
6526         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
6527                 attNames);
6528       }
6529   
6530       /*
6531        * note this throws IllegalStateException if AND-ing to a 
6532        * previously OR-ed compound condition, or vice versa
6533        */
6534       if (and)
6535       {
6536         matcherSet.and(matchCondition);
6537       }
6538       else
6539       {
6540         matcherSet.or(matchCondition);
6541       }
6542     }
6543     else
6544     {
6545       /*
6546        * compound condition
6547        */
6548       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
6549               .getCompoundMatcher().getMatcherSet();
6550       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
6551       if (matchers.size() == 2)
6552       {
6553         parseFilterConditions(matcherSet, matchers.get(0), anded);
6554         parseFilterConditions(matcherSet, matchers.get(1), anded);
6555       }
6556       else
6557       {
6558         System.err.println("Malformed compound filter condition");
6559       }
6560     }
6561   }
6562
6563   /**
6564    * Loads one XML model of a feature colour to a Jalview object
6565    * 
6566    * @param colourModel
6567    * @return
6568    */
6569   public static FeatureColourI parseColour(Colour colourModel)
6570   {
6571     FeatureColourI colour = null;
6572   
6573     if (colourModel.getMax() != null)
6574     {
6575       Color mincol = null;
6576       Color maxcol = null;
6577       Color noValueColour = null;
6578   
6579       try
6580       {
6581         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
6582         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6583       } catch (Exception e)
6584       {
6585         Cache.log.warn("Couldn't parse out graduated feature color.", e);
6586       }
6587   
6588       NoValueColour noCol = colourModel.getNoValueColour();
6589       if (noCol == NoValueColour.MIN)
6590       {
6591         noValueColour = mincol;
6592       }
6593       else if (noCol == NoValueColour.MAX)
6594       {
6595         noValueColour = maxcol;
6596       }
6597   
6598       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
6599               safeFloat(colourModel.getMin()),
6600               safeFloat(colourModel.getMax()));
6601       final List<String> attributeName = colourModel.getAttributeName();
6602       String[] attributes = attributeName
6603               .toArray(new String[attributeName.size()]);
6604       if (attributes != null && attributes.length > 0)
6605       {
6606         colour.setAttributeName(attributes);
6607       }
6608       if (colourModel.isAutoScale() != null)
6609       {
6610         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
6611       }
6612       if (colourModel.isColourByLabel() != null)
6613       {
6614         colour.setColourByLabel(
6615                 colourModel.isColourByLabel().booleanValue());
6616       }
6617       if (colourModel.getThreshold() != null)
6618       {
6619         colour.setThreshold(colourModel.getThreshold().floatValue());
6620       }
6621       ThresholdType ttyp = colourModel.getThreshType();
6622       if (ttyp == ThresholdType.ABOVE)
6623       {
6624         colour.setAboveThreshold(true);
6625       }
6626       else if (ttyp == ThresholdType.BELOW)
6627       {
6628         colour.setBelowThreshold(true);
6629       }
6630     }
6631     else
6632     {
6633       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6634       colour = new FeatureColour(color);
6635     }
6636   
6637     return colour;
6638   }
6639 }