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