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