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