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