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