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