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