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