JAL-3253 jalview.bin.Instance handles all singleton instances -
[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.bin.Instance;
38 import jalview.datamodel.AlignedCodonFrame;
39 import jalview.datamodel.Alignment;
40 import jalview.datamodel.AlignmentAnnotation;
41 import jalview.datamodel.AlignmentI;
42 import jalview.datamodel.DBRefEntry;
43 import jalview.datamodel.GraphLine;
44 import jalview.datamodel.PDBEntry;
45 import jalview.datamodel.Point;
46 import jalview.datamodel.RnaViewerModel;
47 import jalview.datamodel.SequenceFeature;
48 import jalview.datamodel.SequenceGroup;
49 import jalview.datamodel.SequenceI;
50 import jalview.datamodel.StructureViewerModel;
51 import jalview.datamodel.StructureViewerModel.StructureData;
52 import jalview.datamodel.features.FeatureMatcher;
53 import jalview.datamodel.features.FeatureMatcherI;
54 import jalview.datamodel.features.FeatureMatcherSet;
55 import jalview.datamodel.features.FeatureMatcherSetI;
56 import jalview.ext.varna.RnaModel;
57 import jalview.gui.AlignFrame;
58 import jalview.gui.AlignViewport;
59 import jalview.gui.AlignmentPanel;
60 import jalview.gui.AppVarna;
61 import jalview.gui.ChimeraViewFrame;
62 import jalview.gui.Desktop;
63 import jalview.gui.FeatureRenderer;
64 import jalview.gui.JvOptionPane;
65 import jalview.gui.OOMWarning;
66 import jalview.gui.PCAPanel;
67 import jalview.gui.PaintRefresher;
68 import jalview.gui.SplitFrame;
69 import jalview.gui.StructureViewer;
70 import jalview.gui.StructureViewer.ViewerType;
71 import jalview.gui.StructureViewerBase;
72 import jalview.gui.TreePanel;
73 import jalview.io.BackupFiles;
74 import jalview.io.DataSourceType;
75 import jalview.io.FileFormat;
76 import jalview.io.NewickFile;
77 import jalview.math.Matrix;
78 import jalview.math.MatrixI;
79 import jalview.renderer.ResidueShaderI;
80 import jalview.schemes.AnnotationColourGradient;
81 import jalview.schemes.ColourSchemeI;
82 import jalview.schemes.ColourSchemeProperty;
83 import jalview.schemes.FeatureColour;
84 import jalview.schemes.ResidueProperties;
85 import jalview.schemes.UserColourScheme;
86 import jalview.structure.StructureSelectionManager;
87 import jalview.structures.models.AAStructureBindingModel;
88 import jalview.util.Format;
89 import jalview.util.MessageManager;
90 import jalview.util.Platform;
91 import jalview.util.StringUtils;
92 import jalview.util.jarInputStreamProvider;
93 import jalview.util.matcher.Condition;
94 import jalview.viewmodel.AlignmentViewport;
95 import jalview.viewmodel.PCAModel;
96 import jalview.viewmodel.ViewportRanges;
97 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
98 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
99 import jalview.ws.jws2.Jws2Discoverer;
100 import jalview.ws.jws2.dm.AAConSettings;
101 import jalview.ws.jws2.jabaws2.Jws2Instance;
102 import jalview.ws.params.ArgumentI;
103 import jalview.ws.params.AutoCalcSetting;
104 import jalview.ws.params.WsParamSetI;
105 import jalview.xml.binding.jalview.AlcodonFrame;
106 import jalview.xml.binding.jalview.AlcodonFrame.AlcodMap;
107 import jalview.xml.binding.jalview.Annotation;
108 import jalview.xml.binding.jalview.Annotation.ThresholdLine;
109 import jalview.xml.binding.jalview.AnnotationColourScheme;
110 import jalview.xml.binding.jalview.AnnotationElement;
111 import jalview.xml.binding.jalview.DoubleMatrix;
112 import jalview.xml.binding.jalview.DoubleVector;
113 import jalview.xml.binding.jalview.Feature;
114 import jalview.xml.binding.jalview.Feature.OtherData;
115 import jalview.xml.binding.jalview.FeatureMatcherSet.CompoundMatcher;
116 import jalview.xml.binding.jalview.FilterBy;
117 import jalview.xml.binding.jalview.JalviewModel;
118 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings;
119 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Group;
120 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Setting;
121 import jalview.xml.binding.jalview.JalviewModel.JGroup;
122 import jalview.xml.binding.jalview.JalviewModel.JSeq;
123 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids;
124 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids.StructureState;
125 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer;
126 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer.SecondaryStructure;
127 import jalview.xml.binding.jalview.JalviewModel.PcaViewer;
128 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.Axis;
129 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMax;
130 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMin;
131 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SequencePoint;
132 import jalview.xml.binding.jalview.JalviewModel.Tree;
133 import jalview.xml.binding.jalview.JalviewModel.UserColours;
134 import jalview.xml.binding.jalview.JalviewModel.Viewport;
135 import jalview.xml.binding.jalview.JalviewModel.Viewport.CalcIdParam;
136 import jalview.xml.binding.jalview.JalviewModel.Viewport.HiddenColumns;
137 import jalview.xml.binding.jalview.JalviewUserColours;
138 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
139 import jalview.xml.binding.jalview.MapListType.MapListFrom;
140 import jalview.xml.binding.jalview.MapListType.MapListTo;
141 import jalview.xml.binding.jalview.Mapping;
142 import jalview.xml.binding.jalview.NoValueColour;
143 import jalview.xml.binding.jalview.ObjectFactory;
144 import jalview.xml.binding.jalview.PcaDataType;
145 import jalview.xml.binding.jalview.Pdbentry.Property;
146 import jalview.xml.binding.jalview.Sequence;
147 import jalview.xml.binding.jalview.Sequence.DBRef;
148 import jalview.xml.binding.jalview.SequenceSet;
149 import jalview.xml.binding.jalview.SequenceSet.SequenceSetProperties;
150 import jalview.xml.binding.jalview.ThresholdType;
151 import jalview.xml.binding.jalview.VAMSAS;
152
153 import java.awt.Color;
154 import java.awt.Font;
155 import java.awt.Rectangle;
156 import java.io.BufferedReader;
157 import java.io.ByteArrayInputStream;
158 import java.io.DataInputStream;
159 import java.io.DataOutputStream;
160 import java.io.File;
161 import java.io.FileInputStream;
162 import java.io.FileOutputStream;
163 import java.io.IOException;
164 import java.io.InputStreamReader;
165 import java.io.OutputStreamWriter;
166 import java.io.PrintWriter;
167 import java.lang.reflect.InvocationTargetException;
168 import java.math.BigInteger;
169 import java.net.MalformedURLException;
170 import java.net.URL;
171 import java.util.ArrayList;
172 import java.util.Arrays;
173 import java.util.Collections;
174 import java.util.Enumeration;
175 import java.util.GregorianCalendar;
176 import java.util.HashMap;
177 import java.util.HashSet;
178 import java.util.Hashtable;
179 import java.util.IdentityHashMap;
180 import java.util.Iterator;
181 import java.util.LinkedHashMap;
182 import java.util.List;
183 import java.util.Map;
184 import java.util.Map.Entry;
185 import java.util.Set;
186 import java.util.Vector;
187 import java.util.jar.JarEntry;
188 import java.util.jar.JarInputStream;
189 import java.util.jar.JarOutputStream;
190
191 import javax.swing.JInternalFrame;
192 import javax.swing.SwingUtilities;
193 import javax.xml.bind.JAXBContext;
194 import javax.xml.bind.JAXBElement;
195 import javax.xml.bind.Marshaller;
196 import javax.xml.datatype.DatatypeConfigurationException;
197 import javax.xml.datatype.DatatypeFactory;
198 import javax.xml.datatype.XMLGregorianCalendar;
199 import javax.xml.stream.XMLInputFactory;
200 import javax.xml.stream.XMLStreamReader;
201
202 /**
203  * Write out the current jalview desktop state as a Jalview XML stream.
204  * 
205  * Note: the vamsas objects referred to here are primitive versions of the
206  * VAMSAS project schema elements - they are not the same and most likely never
207  * will be :)
208  * 
209  * @author $author$
210  * @version $Revision: 1.134 $
211  */
212 public class Jalview2XML
213 {
214
215   // BH 2018 we add the .jvp binary extension to J2S so that
216   // it will declare that binary when we do the file save from the browser
217
218   static
219   {
220     Platform.addJ2SBinaryType(".jvp?");
221   }
222
223   private static final String VIEWER_PREFIX = "viewer_";
224
225   private static final String RNA_PREFIX = "rna_";
226
227   private static final String UTF_8 = "UTF-8";
228
229   /**
230    * prefix for recovering datasets for alignments with multiple views where
231    * non-existent dataset IDs were written for some views
232    */
233   private static final String UNIQSEQSETID = "uniqueSeqSetId.";
234
235   // use this with nextCounter() to make unique names for entities
236   private int counter = 0;
237
238   /*
239    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
240    * of sequence objects are created.
241    */
242   IdentityHashMap<SequenceI, String> seqsToIds = null;
243
244   /**
245    * jalview XML Sequence ID to jalview sequence object reference (both dataset
246    * and alignment sequences. Populated as XML reps of sequence objects are
247    * created.)
248    */
249   Map<String, SequenceI> seqRefIds = null;
250
251   Map<String, SequenceI> incompleteSeqs = null;
252
253   List<SeqFref> frefedSequence = null;
254
255   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
256
257   /*
258    * Map of reconstructed AlignFrame objects that appear to have come from
259    * SplitFrame objects (have a dna/protein complement view).
260    */
261   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<>();
262
263   /*
264    * Map from displayed rna structure models to their saved session state jar
265    * entry names
266    */
267   private Map<RnaModel, String> rnaSessions = new HashMap<>();
268
269   /**
270    * A helper method for safely using the value of an optional attribute that
271    * may be null if not present in the XML. Answers the boolean value, or false
272    * if null.
273    * 
274    * @param b
275    * @return
276    */
277   public static boolean safeBoolean(Boolean b)
278   {
279     return b == null ? false : b.booleanValue();
280   }
281
282   /**
283    * A helper method for safely using the value of an optional attribute that
284    * may be null if not present in the XML. Answers the integer value, or zero
285    * if null.
286    * 
287    * @param i
288    * @return
289    */
290   public static int safeInt(Integer i)
291   {
292     return i == null ? 0 : i.intValue();
293   }
294
295   /**
296    * A helper method for safely using the value of an optional attribute that
297    * may be null if not present in the XML. Answers the float value, or zero if
298    * null.
299    * 
300    * @param f
301    * @return
302    */
303   public static float safeFloat(Float f)
304   {
305     return f == null ? 0f : f.floatValue();
306   }
307
308   /**
309    * create/return unique hash string for sq
310    * 
311    * @param sq
312    * @return new or existing unique string for sq
313    */
314   String seqHash(SequenceI sq)
315   {
316     if (seqsToIds == null)
317     {
318       initSeqRefs();
319     }
320     if (seqsToIds.containsKey(sq))
321     {
322       return seqsToIds.get(sq);
323     }
324     else
325     {
326       // create sequential key
327       String key = "sq" + (seqsToIds.size() + 1);
328       key = makeHashCode(sq, key); // check we don't have an external reference
329       // for it already.
330       seqsToIds.put(sq, key);
331       return key;
332     }
333   }
334
335   void initSeqRefs()
336   {
337     if (seqsToIds == null)
338     {
339       seqsToIds = new IdentityHashMap<>();
340     }
341     if (seqRefIds == null)
342     {
343       seqRefIds = new HashMap<>();
344     }
345     if (incompleteSeqs == null)
346     {
347       incompleteSeqs = new HashMap<>();
348     }
349     if (frefedSequence == null)
350     {
351       frefedSequence = new ArrayList<>();
352     }
353   }
354
355   public Jalview2XML()
356   {
357   }
358
359   public Jalview2XML(boolean raiseGUI)
360   {
361     this.raiseGUI = raiseGUI;
362   }
363
364   /**
365    * base class for resolving forward references to sequences by their ID
366    * 
367    * @author jprocter
368    *
369    */
370   abstract class SeqFref
371   {
372     String sref;
373
374     String type;
375
376     public SeqFref(String _sref, String type)
377     {
378       sref = _sref;
379       this.type = type;
380     }
381
382     public String getSref()
383     {
384       return sref;
385     }
386
387     public SequenceI getSrefSeq()
388     {
389       return seqRefIds.get(sref);
390     }
391
392     public boolean isResolvable()
393     {
394       return seqRefIds.get(sref) != null;
395     }
396
397     public SequenceI getSrefDatasetSeq()
398     {
399       SequenceI sq = seqRefIds.get(sref);
400       if (sq != null)
401       {
402         while (sq.getDatasetSequence() != null)
403         {
404           sq = sq.getDatasetSequence();
405         }
406       }
407       return sq;
408     }
409
410     /**
411      * @return true if the forward reference was fully resolved
412      */
413     abstract boolean resolve();
414
415     @Override
416     public String toString()
417     {
418       return type + " reference to " + sref;
419     }
420   }
421
422   /**
423    * create forward reference for a mapping
424    * 
425    * @param sref
426    * @param _jmap
427    * @return
428    */
429   public SeqFref newMappingRef(final String sref,
430           final jalview.datamodel.Mapping _jmap)
431   {
432     SeqFref fref = new SeqFref(sref, "Mapping")
433     {
434       public jalview.datamodel.Mapping jmap = _jmap;
435
436       @Override
437       boolean resolve()
438       {
439         SequenceI seq = getSrefDatasetSeq();
440         if (seq == null)
441         {
442           return false;
443         }
444         jmap.setTo(seq);
445         return true;
446       }
447     };
448     return fref;
449   }
450
451   public SeqFref newAlcodMapRef(final String sref,
452           final AlignedCodonFrame _cf,
453           final jalview.datamodel.Mapping _jmap)
454   {
455
456     SeqFref fref = new SeqFref(sref, "Codon Frame")
457     {
458       AlignedCodonFrame cf = _cf;
459
460       public jalview.datamodel.Mapping mp = _jmap;
461
462       @Override
463       public boolean isResolvable()
464       {
465         return super.isResolvable() && mp.getTo() != null;
466       }
467
468       @Override
469       boolean resolve()
470       {
471         SequenceI seq = getSrefDatasetSeq();
472         if (seq == null)
473         {
474           return false;
475         }
476         cf.addMap(seq, mp.getTo(), mp.getMap());
477         return true;
478       }
479     };
480     return fref;
481   }
482
483   public void resolveFrefedSequences()
484   {
485     Iterator<SeqFref> nextFref = frefedSequence.iterator();
486     int toresolve = frefedSequence.size();
487     int unresolved = 0, failedtoresolve = 0;
488     while (nextFref.hasNext())
489     {
490       SeqFref ref = nextFref.next();
491       if (ref.isResolvable())
492       {
493         try
494         {
495           if (ref.resolve())
496           {
497             nextFref.remove();
498           }
499           else
500           {
501             failedtoresolve++;
502           }
503         } catch (Exception x)
504         {
505           System.err.println(
506                   "IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
507                           + ref.getSref());
508           x.printStackTrace();
509           failedtoresolve++;
510         }
511       }
512       else
513       {
514         unresolved++;
515       }
516     }
517     if (unresolved > 0)
518     {
519       System.err.println("Jalview Project Import: There were " + unresolved
520               + " forward references left unresolved on the stack.");
521     }
522     if (failedtoresolve > 0)
523     {
524       System.err.println("SERIOUS! " + failedtoresolve
525               + " resolvable forward references failed to resolve.");
526     }
527     if (incompleteSeqs != null && incompleteSeqs.size() > 0)
528     {
529       System.err.println(
530               "Jalview Project Import: There are " + incompleteSeqs.size()
531                       + " sequences which may have incomplete metadata.");
532       if (incompleteSeqs.size() < 10)
533       {
534         for (SequenceI s : incompleteSeqs.values())
535         {
536           System.err.println(s.toString());
537         }
538       }
539       else
540       {
541         System.err.println(
542                 "Too many to report. Skipping output of incomplete sequences.");
543       }
544     }
545   }
546
547   /**
548    * This maintains a map of viewports, the key being the seqSetId. Important to
549    * set historyItem and redoList for multiple views
550    */
551   Map<String, AlignViewport> viewportsAdded = new HashMap<>();
552
553   Map<String, AlignmentAnnotation> annotationIds = new HashMap<>();
554
555   String uniqueSetSuffix = "";
556
557   /**
558    * List of pdbfiles added to Jar
559    */
560   List<String> pdbfiles = null;
561
562   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
563   public void saveState(File statefile)
564   {
565     FileOutputStream fos = null;
566
567     try
568     {
569
570       fos = new FileOutputStream(statefile);
571
572       JarOutputStream jout = new JarOutputStream(fos);
573       saveState(jout);
574       fos.close();
575
576     } catch (Exception e)
577     {
578       Cache.log.error("Couln't write Jalview state to " + statefile, e);
579       // TODO: inform user of the problem - they need to know if their data was
580       // not saved !
581       if (errorMessage == null)
582       {
583         errorMessage = "Did't write Jalview Archive to output file '"
584                 + statefile + "' - See console error log for details";
585       }
586       else
587       {
588         errorMessage += "(Didn't write Jalview Archive to output file '"
589                 + statefile + ")";
590       }
591       e.printStackTrace();
592     } finally
593     {
594       if (fos != null)
595       {
596         try
597         {
598           fos.close();
599         } catch (IOException e)
600         {
601           // ignore
602         }
603       }
604     }
605     reportErrors();
606   }
607
608   /**
609    * Writes a jalview project archive to the given Jar output stream.
610    * 
611    * @param jout
612    */
613   public void saveState(JarOutputStream jout)
614   {
615     AlignFrame[] frames = Desktop.getAlignFrames();
616
617     if (frames == null)
618     {
619       return;
620     }
621     saveAllFrames(Arrays.asList(frames), jout);
622   }
623
624   /**
625    * core method for storing state for a set of AlignFrames.
626    * 
627    * @param frames
628    *          - frames involving all data to be exported (including containing
629    *          splitframes)
630    * @param jout
631    *          - project output stream
632    */
633   private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
634   {
635     Hashtable<String, AlignFrame> dsses = new Hashtable<>();
636
637     /*
638      * ensure cached data is clear before starting
639      */
640     // todo tidy up seqRefIds, seqsToIds initialisation / reset
641     rnaSessions.clear();
642     splitFrameCandidates.clear();
643
644     try
645     {
646
647       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
648       // //////////////////////////////////////////////////
649
650       List<String> shortNames = new ArrayList<>();
651       List<String> viewIds = new ArrayList<>();
652
653       // REVERSE ORDER
654       for (int i = frames.size() - 1; i > -1; i--)
655       {
656         AlignFrame af = frames.get(i);
657         // skip ?
658         if (skipList != null && skipList
659                 .containsKey(af.getViewport().getSequenceSetId()))
660         {
661           continue;
662         }
663
664         String shortName = makeFilename(af, shortNames);
665
666         int apSize = af.getAlignPanels().size();
667
668         for (int ap = 0; ap < apSize; ap++)
669         {
670           AlignmentPanel apanel = (AlignmentPanel) af.getAlignPanels()
671                   .get(ap);
672           String fileName = apSize == 1 ? shortName : ap + shortName;
673           if (!fileName.endsWith(".xml"))
674           {
675             fileName = fileName + ".xml";
676           }
677
678           saveState(apanel, fileName, jout, viewIds);
679
680           String dssid = getDatasetIdRef(
681                   af.getViewport().getAlignment().getDataset());
682           if (!dsses.containsKey(dssid))
683           {
684             dsses.put(dssid, af);
685           }
686         }
687       }
688
689       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
690               jout);
691
692       try
693       {
694         jout.flush();
695       } catch (Exception foo)
696       {
697       }
698       jout.close();
699     } catch (Exception ex)
700     {
701       // TODO: inform user of the problem - they need to know if their data was
702       // not saved !
703       if (errorMessage == null)
704       {
705         errorMessage = "Couldn't write Jalview Archive - see error output for details";
706       }
707       ex.printStackTrace();
708     }
709   }
710
711   /**
712    * Generates a distinct file name, based on the title of the AlignFrame, by
713    * appending _n for increasing n until an unused name is generated. The new
714    * name (without its extension) is added to the list.
715    * 
716    * @param af
717    * @param namesUsed
718    * @return the generated name, with .xml extension
719    */
720   protected String makeFilename(AlignFrame af, List<String> namesUsed)
721   {
722     String shortName = af.getTitle();
723
724     if (shortName.indexOf(File.separatorChar) > -1)
725     {
726       shortName = shortName
727               .substring(shortName.lastIndexOf(File.separatorChar) + 1);
728     }
729
730     int count = 1;
731
732     while (namesUsed.contains(shortName))
733     {
734       if (shortName.endsWith("_" + (count - 1)))
735       {
736         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
737       }
738
739       shortName = shortName.concat("_" + count);
740       count++;
741     }
742
743     namesUsed.add(shortName);
744
745     if (!shortName.endsWith(".xml"))
746     {
747       shortName = shortName + ".xml";
748     }
749     return shortName;
750   }
751
752   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
753   public boolean saveAlignment(AlignFrame af, String jarFile,
754           String fileName)
755   {
756     try
757     {
758       // create backupfiles object and get new temp filename destination
759       boolean doBackup = BackupFiles.getEnabled();
760       BackupFiles backupfiles = doBackup ? new BackupFiles(jarFile) : null;
761       FileOutputStream fos = new FileOutputStream(doBackup ? 
762               backupfiles.getTempFilePath() : jarFile);
763
764       JarOutputStream jout = new JarOutputStream(fos);
765       List<AlignFrame> frames = new ArrayList<>();
766
767       // resolve splitframes
768       if (af.getViewport().getCodingComplement() != null)
769       {
770         frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames();
771       }
772       else
773       {
774         frames.add(af);
775       }
776       saveAllFrames(frames, jout);
777       try
778       {
779         jout.flush();
780       } catch (Exception foo)
781       {
782       }
783       jout.close();
784       boolean success = true;
785
786       if (doBackup)
787       {
788         backupfiles.setWriteSuccess(success);
789         success = backupfiles.rollBackupsAndRenameTempFile();
790       }
791
792       return success;
793     } catch (Exception ex)
794     {
795       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
796       ex.printStackTrace();
797       return false;
798     }
799   }
800
801   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
802           String fileName, JarOutputStream jout)
803   {
804
805     for (String dssids : dsses.keySet())
806     {
807       AlignFrame _af = dsses.get(dssids);
808       String jfileName = fileName + " Dataset for " + _af.getTitle();
809       if (!jfileName.endsWith(".xml"))
810       {
811         jfileName = jfileName + ".xml";
812       }
813       saveState(_af.alignPanel, jfileName, true, jout, null);
814     }
815   }
816
817   /**
818    * create a JalviewModel from an alignment view and marshall it to a
819    * JarOutputStream
820    * 
821    * @param ap
822    *          panel to create jalview model for
823    * @param fileName
824    *          name of alignment panel written to output stream
825    * @param jout
826    *          jar output stream
827    * @param viewIds
828    * @param out
829    *          jar entry name
830    */
831   public JalviewModel saveState(AlignmentPanel ap, String fileName,
832           JarOutputStream jout, List<String> viewIds)
833   {
834     return saveState(ap, fileName, false, jout, viewIds);
835   }
836
837   /**
838    * create a JalviewModel from an alignment view and marshall it to a
839    * JarOutputStream
840    * 
841    * @param ap
842    *          panel to create jalview model for
843    * @param fileName
844    *          name of alignment panel written to output stream
845    * @param storeDS
846    *          when true, only write the dataset for the alignment, not the data
847    *          associated with the view.
848    * @param jout
849    *          jar output stream
850    * @param out
851    *          jar entry name
852    */
853   public JalviewModel saveState(AlignmentPanel ap, String fileName,
854           boolean storeDS, JarOutputStream jout, List<String> viewIds)
855   {
856     if (viewIds == null)
857     {
858       viewIds = new ArrayList<>();
859     }
860
861     initSeqRefs();
862
863     List<UserColourScheme> userColours = new ArrayList<>();
864
865     AlignViewport av = ap.av;
866     ViewportRanges vpRanges = av.getRanges();
867
868     final ObjectFactory objectFactory = new ObjectFactory();
869     JalviewModel object = objectFactory.createJalviewModel();
870     object.setVamsasModel(new VAMSAS());
871
872     // object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
873     try
874     {
875       GregorianCalendar c = new GregorianCalendar();
876       DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
877       XMLGregorianCalendar now = datatypeFactory.newXMLGregorianCalendar(c);// gregorianCalendar);
878       object.setCreationDate(now);
879     } catch (DatatypeConfigurationException e)
880     {
881       System.err.println("error writing date: " + e.toString());
882     }
883     object.setVersion(
884             jalview.bin.Cache.getDefault("VERSION", "Development Build"));
885
886     /**
887      * rjal is full height alignment, jal is actual alignment with full metadata
888      * but excludes hidden sequences.
889      */
890     jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
891
892     if (av.hasHiddenRows())
893     {
894       rjal = jal.getHiddenSequences().getFullAlignment();
895     }
896
897     SequenceSet vamsasSet = new SequenceSet();
898     Sequence vamsasSeq;
899     // JalviewModelSequence jms = new JalviewModelSequence();
900
901     vamsasSet.setGapChar(jal.getGapCharacter() + "");
902
903     if (jal.getDataset() != null)
904     {
905       // dataset id is the dataset's hashcode
906       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
907       if (storeDS)
908       {
909         // switch jal and the dataset
910         jal = jal.getDataset();
911         rjal = jal;
912       }
913     }
914     if (jal.getProperties() != null)
915     {
916       Enumeration en = jal.getProperties().keys();
917       while (en.hasMoreElements())
918       {
919         String key = en.nextElement().toString();
920         SequenceSetProperties ssp = new SequenceSetProperties();
921         ssp.setKey(key);
922         ssp.setValue(jal.getProperties().get(key).toString());
923         // vamsasSet.addSequenceSetProperties(ssp);
924         vamsasSet.getSequenceSetProperties().add(ssp);
925       }
926     }
927
928     JSeq jseq;
929     Set<String> calcIdSet = new HashSet<>();
930     // record the set of vamsas sequence XML POJO we create.
931     HashMap<String, Sequence> vamsasSetIds = new HashMap<>();
932     // SAVE SEQUENCES
933     for (final SequenceI jds : rjal.getSequences())
934     {
935       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
936               : jds.getDatasetSequence();
937       String id = seqHash(jds);
938       if (vamsasSetIds.get(id) == null)
939       {
940         if (seqRefIds.get(id) != null && !storeDS)
941         {
942           // This happens for two reasons: 1. multiple views are being
943           // serialised.
944           // 2. the hashCode has collided with another sequence's code. This
945           // DOES
946           // HAPPEN! (PF00072.15.stk does this)
947           // JBPNote: Uncomment to debug writing out of files that do not read
948           // back in due to ArrayOutOfBoundExceptions.
949           // System.err.println("vamsasSeq backref: "+id+"");
950           // System.err.println(jds.getName()+"
951           // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
952           // System.err.println("Hashcode: "+seqHash(jds));
953           // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
954           // System.err.println(rsq.getName()+"
955           // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
956           // System.err.println("Hashcode: "+seqHash(rsq));
957         }
958         else
959         {
960           vamsasSeq = createVamsasSequence(id, jds);
961 //          vamsasSet.addSequence(vamsasSeq);
962           vamsasSet.getSequence().add(vamsasSeq);
963           vamsasSetIds.put(id, vamsasSeq);
964           seqRefIds.put(id, jds);
965         }
966       }
967       jseq = new JSeq();
968       jseq.setStart(jds.getStart());
969       jseq.setEnd(jds.getEnd());
970       jseq.setColour(av.getSequenceColour(jds).getRGB());
971
972       jseq.setId(id); // jseq id should be a string not a number
973       if (!storeDS)
974       {
975         // Store any sequences this sequence represents
976         if (av.hasHiddenRows())
977         {
978           // use rjal, contains the full height alignment
979           jseq.setHidden(
980                   av.getAlignment().getHiddenSequences().isHidden(jds));
981
982           if (av.isHiddenRepSequence(jds))
983           {
984             jalview.datamodel.SequenceI[] reps = av
985                     .getRepresentedSequences(jds).getSequencesInOrder(rjal);
986
987             for (int h = 0; h < reps.length; h++)
988             {
989               if (reps[h] != jds)
990               {
991                 // jseq.addHiddenSequences(rjal.findIndex(reps[h]));
992                 jseq.getHiddenSequences().add(rjal.findIndex(reps[h]));
993               }
994             }
995           }
996         }
997         // mark sequence as reference - if it is the reference for this view
998         if (jal.hasSeqrep())
999         {
1000           jseq.setViewreference(jds == jal.getSeqrep());
1001         }
1002       }
1003
1004       // TODO: omit sequence features from each alignment view's XML dump if we
1005       // are storing dataset
1006       List<SequenceFeature> sfs = jds.getSequenceFeatures();
1007       for (SequenceFeature sf : sfs)
1008       {
1009         // Features features = new Features();
1010         Feature features = new Feature();
1011
1012         features.setBegin(sf.getBegin());
1013         features.setEnd(sf.getEnd());
1014         features.setDescription(sf.getDescription());
1015         features.setType(sf.getType());
1016         features.setFeatureGroup(sf.getFeatureGroup());
1017         features.setScore(sf.getScore());
1018         if (sf.links != null)
1019         {
1020           for (int l = 0; l < sf.links.size(); l++)
1021           {
1022             OtherData keyValue = new OtherData();
1023             keyValue.setKey("LINK_" + l);
1024             keyValue.setValue(sf.links.elementAt(l).toString());
1025             // features.addOtherData(keyValue);
1026             features.getOtherData().add(keyValue);
1027           }
1028         }
1029         if (sf.otherDetails != null)
1030         {
1031           /*
1032            * save feature attributes, which may be simple strings or
1033            * map valued (have sub-attributes)
1034            */
1035           for (Entry<String, Object> entry : sf.otherDetails.entrySet())
1036           {
1037             String key = entry.getKey();
1038             Object value = entry.getValue();
1039             if (value instanceof Map<?, ?>)
1040             {
1041               for (Entry<String, Object> subAttribute : ((Map<String, Object>) value)
1042                       .entrySet())
1043               {
1044                 OtherData otherData = new OtherData();
1045                 otherData.setKey(key);
1046                 otherData.setKey2(subAttribute.getKey());
1047                 otherData.setValue(subAttribute.getValue().toString());
1048                 // features.addOtherData(otherData);
1049                 features.getOtherData().add(otherData);
1050               }
1051             }
1052             else
1053             {
1054               OtherData otherData = new OtherData();
1055               otherData.setKey(key);
1056               otherData.setValue(value.toString());
1057               // features.addOtherData(otherData);
1058               features.getOtherData().add(otherData);
1059             }
1060           }
1061         }
1062
1063         // jseq.addFeatures(features);
1064         jseq.getFeatures().add(features);
1065       }
1066
1067       if (jdatasq.getAllPDBEntries() != null)
1068       {
1069         Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
1070         while (en.hasMoreElements())
1071         {
1072           Pdbids pdb = new Pdbids();
1073           jalview.datamodel.PDBEntry entry = en.nextElement();
1074
1075           String pdbId = entry.getId();
1076           pdb.setId(pdbId);
1077           pdb.setType(entry.getType());
1078
1079           /*
1080            * Store any structure views associated with this sequence. This
1081            * section copes with duplicate entries in the project, so a dataset
1082            * only view *should* be coped with sensibly.
1083            */
1084           // This must have been loaded, is it still visible?
1085           JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
1086           String matchedFile = null;
1087           for (int f = frames.length - 1; f > -1; f--)
1088           {
1089             if (frames[f] instanceof StructureViewerBase)
1090             {
1091               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
1092               matchedFile = saveStructureState(ap, jds, pdb, entry, viewIds,
1093                       matchedFile, viewFrame);
1094               /*
1095                * Only store each structure viewer's state once in the project
1096                * jar. First time through only (storeDS==false)
1097                */
1098               String viewId = viewFrame.getViewId();
1099               if (!storeDS && !viewIds.contains(viewId))
1100               {
1101                 viewIds.add(viewId);
1102                 try
1103                 {
1104                   String viewerState = viewFrame.getStateInfo();
1105                   writeJarEntry(jout, getViewerJarEntryName(viewId),
1106                           viewerState.getBytes());
1107                 } catch (IOException e)
1108                 {
1109                   System.err.println(
1110                           "Error saving viewer state: " + e.getMessage());
1111                 }
1112               }
1113             }
1114           }
1115
1116           if (matchedFile != null || entry.getFile() != null)
1117           {
1118             if (entry.getFile() != null)
1119             {
1120               // use entry's file
1121               matchedFile = entry.getFile();
1122             }
1123             pdb.setFile(matchedFile); // entry.getFile());
1124             if (pdbfiles == null)
1125             {
1126               pdbfiles = new ArrayList<>();
1127             }
1128
1129             if (!pdbfiles.contains(pdbId))
1130             {
1131               pdbfiles.add(pdbId);
1132               copyFileToJar(jout, matchedFile, pdbId);
1133             }
1134           }
1135
1136           Enumeration<String> props = entry.getProperties();
1137           if (props.hasMoreElements())
1138           {
1139             // PdbentryItem item = new PdbentryItem();
1140             while (props.hasMoreElements())
1141             {
1142               Property prop = new Property();
1143               String key = props.nextElement();
1144               prop.setName(key);
1145               prop.setValue(entry.getProperty(key).toString());
1146               // item.addProperty(prop);
1147               pdb.getProperty().add(prop);
1148             }
1149             // pdb.addPdbentryItem(item);
1150           }
1151
1152           // jseq.addPdbids(pdb);
1153           jseq.getPdbids().add(pdb);
1154         }
1155       }
1156
1157       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
1158
1159       // jms.addJSeq(jseq);
1160       object.getJSeq().add(jseq);
1161     }
1162
1163     if (!storeDS && av.hasHiddenRows())
1164     {
1165       jal = av.getAlignment();
1166     }
1167     // SAVE MAPPINGS
1168     // FOR DATASET
1169     if (storeDS && jal.getCodonFrames() != null)
1170     {
1171       List<AlignedCodonFrame> jac = jal.getCodonFrames();
1172       for (AlignedCodonFrame acf : jac)
1173       {
1174         AlcodonFrame alc = new AlcodonFrame();
1175         if (acf.getProtMappings() != null
1176                 && acf.getProtMappings().length > 0)
1177         {
1178           boolean hasMap = false;
1179           SequenceI[] dnas = acf.getdnaSeqs();
1180           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1181           for (int m = 0; m < pmaps.length; m++)
1182           {
1183             AlcodMap alcmap = new AlcodMap();
1184             alcmap.setDnasq(seqHash(dnas[m]));
1185             alcmap.setMapping(
1186                     createVamsasMapping(pmaps[m], dnas[m], null, false));
1187             // alc.addAlcodMap(alcmap);
1188             alc.getAlcodMap().add(alcmap);
1189             hasMap = true;
1190           }
1191           if (hasMap)
1192           {
1193             // vamsasSet.addAlcodonFrame(alc);
1194             vamsasSet.getAlcodonFrame().add(alc);
1195           }
1196         }
1197         // TODO: delete this ? dead code from 2.8.3->2.9 ?
1198         // {
1199         // AlcodonFrame alc = new AlcodonFrame();
1200         // vamsasSet.addAlcodonFrame(alc);
1201         // for (int p = 0; p < acf.aaWidth; p++)
1202         // {
1203         // Alcodon cmap = new Alcodon();
1204         // if (acf.codons[p] != null)
1205         // {
1206         // // Null codons indicate a gapped column in the translated peptide
1207         // // alignment.
1208         // cmap.setPos1(acf.codons[p][0]);
1209         // cmap.setPos2(acf.codons[p][1]);
1210         // cmap.setPos3(acf.codons[p][2]);
1211         // }
1212         // alc.addAlcodon(cmap);
1213         // }
1214         // if (acf.getProtMappings() != null
1215         // && acf.getProtMappings().length > 0)
1216         // {
1217         // SequenceI[] dnas = acf.getdnaSeqs();
1218         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1219         // for (int m = 0; m < pmaps.length; m++)
1220         // {
1221         // AlcodMap alcmap = new AlcodMap();
1222         // alcmap.setDnasq(seqHash(dnas[m]));
1223         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
1224         // false));
1225         // alc.addAlcodMap(alcmap);
1226         // }
1227         // }
1228       }
1229     }
1230
1231     // SAVE TREES
1232     // /////////////////////////////////
1233     if (!storeDS && av.getCurrentTree() != null)
1234     {
1235       // FIND ANY ASSOCIATED TREES
1236       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
1237       if (Desktop.getDesktopPane() != null)
1238       {
1239         JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
1240
1241         for (int t = 0; t < frames.length; t++)
1242         {
1243           if (frames[t] instanceof TreePanel)
1244           {
1245             TreePanel tp = (TreePanel) frames[t];
1246
1247             if (tp.getTreeCanvas().getViewport().getAlignment() == jal)
1248             {
1249               JalviewModel.Tree tree = new JalviewModel.Tree();
1250               tree.setTitle(tp.getTitle());
1251               tree.setCurrentTree((av.getCurrentTree() == tp.getTree()));
1252               tree.setNewick(tp.getTree().print());
1253               tree.setThreshold(tp.getTreeCanvas().getThreshold());
1254
1255               tree.setFitToWindow(tp.fitToWindow.getState());
1256               tree.setFontName(tp.getTreeFont().getName());
1257               tree.setFontSize(tp.getTreeFont().getSize());
1258               tree.setFontStyle(tp.getTreeFont().getStyle());
1259               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
1260
1261               tree.setShowBootstrap(tp.bootstrapMenu.getState());
1262               tree.setShowDistances(tp.distanceMenu.getState());
1263
1264               tree.setHeight(tp.getHeight());
1265               tree.setWidth(tp.getWidth());
1266               tree.setXpos(tp.getX());
1267               tree.setYpos(tp.getY());
1268               tree.setId(makeHashCode(tp, null));
1269               tree.setLinkToAllViews(
1270                       tp.getTreeCanvas().isApplyToAllViews());
1271
1272               // jms.addTree(tree);
1273               object.getTree().add(tree);
1274             }
1275           }
1276         }
1277       }
1278     }
1279
1280     /*
1281      * save PCA viewers
1282      */
1283     if (!storeDS && Desktop.getDesktopPane() != null)
1284     {
1285       for (JInternalFrame frame : Desktop.getDesktopPane().getAllFrames())
1286       {
1287         if (frame instanceof PCAPanel)
1288         {
1289           PCAPanel panel = (PCAPanel) frame;
1290           if (panel.getAlignViewport().getAlignment() == jal)
1291           {
1292             savePCA(panel, object);
1293           }
1294         }
1295       }
1296     }
1297
1298     // SAVE ANNOTATIONS
1299     /**
1300      * store forward refs from an annotationRow to any groups
1301      */
1302     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<>();
1303     if (storeDS)
1304     {
1305       for (SequenceI sq : jal.getSequences())
1306       {
1307         // Store annotation on dataset sequences only
1308         AlignmentAnnotation[] aa = sq.getAnnotation();
1309         if (aa != null && aa.length > 0)
1310         {
1311           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1312                   vamsasSet);
1313         }
1314       }
1315     }
1316     else
1317     {
1318       if (jal.getAlignmentAnnotation() != null)
1319       {
1320         // Store the annotation shown on the alignment.
1321         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
1322         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1323                 vamsasSet);
1324       }
1325     }
1326     // SAVE GROUPS
1327     if (jal.getGroups() != null)
1328     {
1329       JGroup[] groups = new JGroup[jal.getGroups().size()];
1330       int i = -1;
1331       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
1332       {
1333         JGroup jGroup = new JGroup();
1334         groups[++i] = jGroup;
1335
1336         jGroup.setStart(sg.getStartRes());
1337         jGroup.setEnd(sg.getEndRes());
1338         jGroup.setName(sg.getName());
1339         if (groupRefs.containsKey(sg))
1340         {
1341           // group has references so set its ID field
1342           jGroup.setId(groupRefs.get(sg));
1343         }
1344         ColourSchemeI colourScheme = sg.getColourScheme();
1345         if (colourScheme != null)
1346         {
1347           ResidueShaderI groupColourScheme = sg.getGroupColourScheme();
1348           if (groupColourScheme.conservationApplied())
1349           {
1350             jGroup.setConsThreshold(groupColourScheme.getConservationInc());
1351
1352             if (colourScheme instanceof jalview.schemes.UserColourScheme)
1353             {
1354               jGroup.setColour(
1355                       setUserColourScheme(colourScheme, userColours,
1356                               object));
1357             }
1358             else
1359             {
1360               jGroup.setColour(colourScheme.getSchemeName());
1361             }
1362           }
1363           else if (colourScheme instanceof jalview.schemes.AnnotationColourGradient)
1364           {
1365             jGroup.setColour("AnnotationColourGradient");
1366             jGroup.setAnnotationColours(constructAnnotationColours(
1367                     (jalview.schemes.AnnotationColourGradient) colourScheme,
1368                     userColours, object));
1369           }
1370           else if (colourScheme instanceof jalview.schemes.UserColourScheme)
1371           {
1372             jGroup.setColour(
1373                     setUserColourScheme(colourScheme, userColours, object));
1374           }
1375           else
1376           {
1377             jGroup.setColour(colourScheme.getSchemeName());
1378           }
1379
1380           jGroup.setPidThreshold(groupColourScheme.getThreshold());
1381         }
1382
1383         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1384         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1385         jGroup.setDisplayText(sg.getDisplayText());
1386         jGroup.setColourText(sg.getColourText());
1387         jGroup.setTextCol1(sg.textColour.getRGB());
1388         jGroup.setTextCol2(sg.textColour2.getRGB());
1389         jGroup.setTextColThreshold(sg.thresholdTextColour);
1390         jGroup.setShowUnconserved(sg.getShowNonconserved());
1391         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1392         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1393         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1394         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1395         for (SequenceI seq : sg.getSequences())
1396         {
1397           // jGroup.addSeq(seqHash(seq));
1398           jGroup.getSeq().add(seqHash(seq));
1399         }
1400       }
1401
1402       //jms.setJGroup(groups);
1403       Object group;
1404       for (JGroup grp : groups)
1405       {
1406         object.getJGroup().add(grp);
1407       }
1408     }
1409     if (!storeDS)
1410     {
1411       // /////////SAVE VIEWPORT
1412       Viewport view = new Viewport();
1413       view.setTitle(ap.alignFrame.getTitle());
1414       view.setSequenceSetId(
1415               makeHashCode(av.getSequenceSetId(), av.getSequenceSetId()));
1416       view.setId(av.getViewId());
1417       if (av.getCodingComplement() != null)
1418       {
1419         view.setComplementId(av.getCodingComplement().getViewId());
1420       }
1421       view.setViewName(av.getViewName());
1422       view.setGatheredViews(av.isGatherViewsHere());
1423
1424       Rectangle size = ap.av.getExplodedGeometry();
1425       Rectangle position = size;
1426       if (size == null)
1427       {
1428         size = ap.alignFrame.getBounds();
1429         if (av.getCodingComplement() != null)
1430         {
1431           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
1432                   .getBounds();
1433         }
1434         else
1435         {
1436           position = size;
1437         }
1438       }
1439       view.setXpos(position.x);
1440       view.setYpos(position.y);
1441
1442       view.setWidth(size.width);
1443       view.setHeight(size.height);
1444
1445       view.setStartRes(vpRanges.getStartRes());
1446       view.setStartSeq(vpRanges.getStartSeq());
1447
1448       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1449       {
1450         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1451                 userColours, object));
1452       }
1453       else if (av
1454               .getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
1455       {
1456         AnnotationColourScheme ac = constructAnnotationColours(
1457                 (jalview.schemes.AnnotationColourGradient) av
1458                         .getGlobalColourScheme(),
1459                 userColours, object);
1460
1461         view.setAnnotationColours(ac);
1462         view.setBgColour("AnnotationColourGradient");
1463       }
1464       else
1465       {
1466         view.setBgColour(ColourSchemeProperty
1467                 .getColourName(av.getGlobalColourScheme()));
1468       }
1469
1470       ResidueShaderI vcs = av.getResidueShading();
1471       ColourSchemeI cs = av.getGlobalColourScheme();
1472
1473       if (cs != null)
1474       {
1475         if (vcs.conservationApplied())
1476         {
1477           view.setConsThreshold(vcs.getConservationInc());
1478           if (cs instanceof jalview.schemes.UserColourScheme)
1479           {
1480             view.setBgColour(setUserColourScheme(cs, userColours, object));
1481           }
1482         }
1483         view.setPidThreshold(vcs.getThreshold());
1484       }
1485
1486       view.setConservationSelected(av.getConservationSelected());
1487       view.setPidSelected(av.getAbovePIDThreshold());
1488       final Font font = av.getFont();
1489       view.setFontName(font.getName());
1490       view.setFontSize(font.getSize());
1491       view.setFontStyle(font.getStyle());
1492       view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
1493       view.setRenderGaps(av.isRenderGaps());
1494       view.setShowAnnotation(av.isShowAnnotation());
1495       view.setShowBoxes(av.getShowBoxes());
1496       view.setShowColourText(av.getColourText());
1497       view.setShowFullId(av.getShowJVSuffix());
1498       view.setRightAlignIds(av.isRightAlignIds());
1499       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
1500       view.setShowText(av.getShowText());
1501       view.setShowUnconserved(av.getShowUnconserved());
1502       view.setWrapAlignment(av.getWrapAlignment());
1503       view.setTextCol1(av.getTextColour().getRGB());
1504       view.setTextCol2(av.getTextColour2().getRGB());
1505       view.setTextColThreshold(av.getThresholdTextColour());
1506       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
1507       view.setShowSequenceLogo(av.isShowSequenceLogo());
1508       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
1509       view.setShowGroupConsensus(av.isShowGroupConsensus());
1510       view.setShowGroupConservation(av.isShowGroupConservation());
1511       view.setShowNPfeatureTooltip(av.isShowNPFeats());
1512       view.setShowDbRefTooltip(av.isShowDBRefs());
1513       view.setFollowHighlight(av.isFollowHighlight());
1514       view.setFollowSelection(av.followSelection);
1515       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
1516       if (av.getFeaturesDisplayed() != null)
1517       {
1518         FeatureSettings fs = new FeatureSettings();
1519
1520         FeatureRenderer fr = ap.getSeqPanel().seqCanvas
1521                 .getFeatureRenderer();
1522         String[] renderOrder = fr.getRenderOrder().toArray(new String[0]);
1523
1524         Vector<String> settingsAdded = new Vector<>();
1525         if (renderOrder != null)
1526         {
1527           for (String featureType : renderOrder)
1528           {
1529             FeatureSettings.Setting setting = new FeatureSettings.Setting();
1530             setting.setType(featureType);
1531
1532             /*
1533              * save any filter for the feature type
1534              */
1535             FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1536             if (filter != null)  {
1537               Iterator<FeatureMatcherI> filters = filter.getMatchers().iterator();
1538               FeatureMatcherI firstFilter = filters.next();
1539               setting.setMatcherSet(Jalview2XML.marshalFilter(
1540                       firstFilter, filters, filter.isAnded()));
1541             }
1542
1543             /*
1544              * save colour scheme for the feature type
1545              */
1546             FeatureColourI fcol = fr.getFeatureStyle(featureType);
1547             if (!fcol.isSimpleColour())
1548             {
1549               setting.setColour(fcol.getMaxColour().getRGB());
1550               setting.setMincolour(fcol.getMinColour().getRGB());
1551               setting.setMin(fcol.getMin());
1552               setting.setMax(fcol.getMax());
1553               setting.setColourByLabel(fcol.isColourByLabel());
1554               if (fcol.isColourByAttribute())
1555               {
1556                 String[] attName = fcol.getAttributeName();
1557                 setting.getAttributeName().add(attName[0]);
1558                 if (attName.length > 1)
1559                 {
1560                   setting.getAttributeName().add(attName[1]);
1561                 }
1562               }
1563               setting.setAutoScale(fcol.isAutoScaled());
1564               setting.setThreshold(fcol.getThreshold());
1565               Color noColour = fcol.getNoColour();
1566               if (noColour == null)
1567               {
1568                 setting.setNoValueColour(NoValueColour.NONE);
1569               }
1570               else if (noColour.equals(fcol.getMaxColour()))
1571               {
1572                 setting.setNoValueColour(NoValueColour.MAX);
1573               }
1574               else
1575               {
1576                 setting.setNoValueColour(NoValueColour.MIN);
1577               }
1578               // -1 = No threshold, 0 = Below, 1 = Above
1579               setting.setThreshstate(fcol.isAboveThreshold() ? 1
1580                       : (fcol.isBelowThreshold() ? 0 : -1));
1581             }
1582             else
1583             {
1584               setting.setColour(fcol.getColour().getRGB());
1585             }
1586
1587             setting.setDisplay(
1588                     av.getFeaturesDisplayed().isVisible(featureType));
1589             float rorder = fr
1590                     .getOrder(featureType);
1591             if (rorder > -1)
1592             {
1593               setting.setOrder(rorder);
1594             }
1595             /// fs.addSetting(setting);
1596             fs.getSetting().add(setting);
1597             settingsAdded.addElement(featureType);
1598           }
1599         }
1600
1601         // is groups actually supposed to be a map here ?
1602         Iterator<String> en = fr.getFeatureGroups().iterator();
1603         Vector<String> groupsAdded = new Vector<>();
1604         while (en.hasNext())
1605         {
1606           String grp = en.next();
1607           if (groupsAdded.contains(grp))
1608           {
1609             continue;
1610           }
1611           Group g = new Group();
1612           g.setName(grp);
1613           g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
1614                           .booleanValue());
1615           // fs.addGroup(g);
1616           fs.getGroup().add(g);
1617           groupsAdded.addElement(grp);
1618         }
1619         // jms.setFeatureSettings(fs);
1620         object.setFeatureSettings(fs);
1621       }
1622
1623       if (av.hasHiddenColumns())
1624       {
1625         jalview.datamodel.HiddenColumns hidden = av.getAlignment()
1626                 .getHiddenColumns();
1627         if (hidden == null)
1628         {
1629           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1630         }
1631         else
1632         {
1633           Iterator<int[]> hiddenRegions = hidden.iterator();
1634           while (hiddenRegions.hasNext())
1635           {
1636             int[] region = hiddenRegions.next();
1637             HiddenColumns hc = new HiddenColumns();
1638             hc.setStart(region[0]);
1639             hc.setEnd(region[1]);
1640             // view.addHiddenColumns(hc);
1641             view.getHiddenColumns().add(hc);
1642           }
1643         }
1644       }
1645       if (calcIdSet.size() > 0)
1646       {
1647         for (String calcId : calcIdSet)
1648         {
1649           if (calcId.trim().length() > 0)
1650           {
1651             CalcIdParam cidp = createCalcIdParam(calcId, av);
1652             // Some calcIds have no parameters.
1653             if (cidp != null)
1654             {
1655               // view.addCalcIdParam(cidp);
1656               view.getCalcIdParam().add(cidp);
1657             }
1658           }
1659         }
1660       }
1661
1662       // jms.addViewport(view);
1663       object.getViewport().add(view);
1664     }
1665     // object.setJalviewModelSequence(jms);
1666     // object.getVamsasModel().addSequenceSet(vamsasSet);
1667     object.getVamsasModel().getSequenceSet().add(vamsasSet);
1668
1669     if (jout != null && fileName != null)
1670     {
1671       // We may not want to write the object to disk,
1672       // eg we can copy the alignViewport to a new view object
1673       // using save and then load
1674       try
1675       {
1676         fileName = fileName.replace('\\', '/');
1677         System.out.println("Writing jar entry " + fileName);
1678         JarEntry entry = new JarEntry(fileName);
1679         jout.putNextEntry(entry);
1680         PrintWriter pout = new PrintWriter(
1681                 new OutputStreamWriter(jout, UTF_8));
1682         JAXBContext jaxbContext = JAXBContext
1683                 .newInstance(JalviewModel.class);
1684         Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1685
1686         // output pretty printed
1687         // jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
1688         jaxbMarshaller.marshal(
1689                 new ObjectFactory().createJalviewModel(object), pout);
1690
1691         // jaxbMarshaller.marshal(object, pout);
1692         // marshaller.marshal(object);
1693         pout.flush();
1694         jout.closeEntry();
1695       } catch (Exception ex)
1696       {
1697         // TODO: raise error in GUI if marshalling failed.
1698         System.err.println("Error writing Jalview project");
1699         ex.printStackTrace();
1700       }
1701     }
1702     return object;
1703   }
1704
1705   /**
1706    * Writes PCA viewer attributes and computed values to an XML model object and
1707    * adds it to the JalviewModel. Any exceptions are reported by logging.
1708    */
1709   protected void savePCA(PCAPanel panel, JalviewModel object)
1710   {
1711     try
1712     {
1713       PcaViewer viewer = new PcaViewer();
1714       viewer.setHeight(panel.getHeight());
1715       viewer.setWidth(panel.getWidth());
1716       viewer.setXpos(panel.getX());
1717       viewer.setYpos(panel.getY());
1718       viewer.setTitle(panel.getTitle());
1719       PCAModel pcaModel = panel.getPcaModel();
1720       viewer.setScoreModelName(pcaModel.getScoreModelName());
1721       viewer.setXDim(panel.getSelectedDimensionIndex(X));
1722       viewer.setYDim(panel.getSelectedDimensionIndex(Y));
1723       viewer.setZDim(panel.getSelectedDimensionIndex(Z));
1724       viewer.setBgColour(
1725               panel.getRotatableCanvas().getBackgroundColour().getRGB());
1726       viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor());
1727       float[] spMin = panel.getRotatableCanvas().getSeqMin();
1728       SeqPointMin spmin = new SeqPointMin();
1729       spmin.setXPos(spMin[0]);
1730       spmin.setYPos(spMin[1]);
1731       spmin.setZPos(spMin[2]);
1732       viewer.setSeqPointMin(spmin);
1733       float[] spMax = panel.getRotatableCanvas().getSeqMax();
1734       SeqPointMax spmax = new SeqPointMax();
1735       spmax.setXPos(spMax[0]);
1736       spmax.setYPos(spMax[1]);
1737       spmax.setZPos(spMax[2]);
1738       viewer.setSeqPointMax(spmax);
1739       viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels());
1740       viewer.setLinkToAllViews(
1741               panel.getRotatableCanvas().isApplyToAllViews());
1742       SimilarityParamsI sp = pcaModel.getSimilarityParameters();
1743       viewer.setIncludeGaps(sp.includeGaps());
1744       viewer.setMatchGaps(sp.matchGaps());
1745       viewer.setIncludeGappedColumns(sp.includeGappedColumns());
1746       viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
1747
1748       /*
1749        * sequence points on display
1750        */
1751       for (jalview.datamodel.SequencePoint spt : pcaModel
1752               .getSequencePoints())
1753       {
1754         SequencePoint point = new SequencePoint();
1755         point.setSequenceRef(seqHash(spt.getSequence()));
1756         point.setXPos(spt.coord.x);
1757         point.setYPos(spt.coord.y);
1758         point.setZPos(spt.coord.z);
1759         viewer.getSequencePoint().add(point);
1760       }
1761
1762       /*
1763        * (end points of) axes on display
1764        */
1765       for (Point p : panel.getRotatableCanvas().getAxisEndPoints())
1766       {
1767
1768         Axis axis = new Axis();
1769         axis.setXPos(p.x);
1770         axis.setYPos(p.y);
1771         axis.setZPos(p.z);
1772         viewer.getAxis().add(axis);
1773       }
1774
1775       /*
1776        * raw PCA data (note we are not restoring PCA inputs here -
1777        * alignment view, score model, similarity parameters)
1778        */
1779       PcaDataType data = new PcaDataType();
1780       viewer.setPcaData(data);
1781       PCA pca = pcaModel.getPcaData();
1782
1783       DoubleMatrix pm = new DoubleMatrix();
1784       saveDoubleMatrix(pca.getPairwiseScores(), pm);
1785       data.setPairwiseMatrix(pm);
1786
1787       DoubleMatrix tm = new DoubleMatrix();
1788       saveDoubleMatrix(pca.getTridiagonal(), tm);
1789       data.setTridiagonalMatrix(tm);
1790
1791       DoubleMatrix eigenMatrix = new DoubleMatrix();
1792       data.setEigenMatrix(eigenMatrix);
1793       saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix);
1794
1795       object.getPcaViewer().add(viewer);
1796     } catch (Throwable t)
1797     {
1798       Cache.log.error("Error saving PCA: " + t.getMessage());
1799     }
1800   }
1801
1802   /**
1803    * Stores values from a matrix into an XML element, including (if present) the
1804    * D or E vectors
1805    * 
1806    * @param m
1807    * @param xmlMatrix
1808    * @see #loadDoubleMatrix(DoubleMatrix)
1809    */
1810   protected void saveDoubleMatrix(MatrixI m, DoubleMatrix xmlMatrix)
1811   {
1812     xmlMatrix.setRows(m.height());
1813     xmlMatrix.setColumns(m.width());
1814     for (int i = 0; i < m.height(); i++)
1815     {
1816       DoubleVector row = new DoubleVector();
1817       for (int j = 0; j < m.width(); j++)
1818       {
1819         row.getV().add(m.getValue(i, j));
1820       }
1821       xmlMatrix.getRow().add(row);
1822     }
1823     if (m.getD() != null)
1824     {
1825       DoubleVector dVector = new DoubleVector();
1826       for (double d : m.getD())
1827       {
1828         dVector.getV().add(d);
1829       }
1830       xmlMatrix.setD(dVector);
1831     }
1832     if (m.getE() != null)
1833     {
1834       DoubleVector eVector = new DoubleVector();
1835       for (double e : m.getE())
1836       {
1837         eVector.getV().add(e);
1838       }
1839       xmlMatrix.setE(eVector);
1840     }
1841   }
1842
1843   /**
1844    * Loads XML matrix data into a new Matrix object, including the D and/or E
1845    * vectors (if present)
1846    * 
1847    * @param mData
1848    * @return
1849    * @see Jalview2XML#saveDoubleMatrix(MatrixI, DoubleMatrix)
1850    */
1851   protected MatrixI loadDoubleMatrix(DoubleMatrix mData)
1852   {
1853     int rows = mData.getRows();
1854     double[][] vals = new double[rows][];
1855
1856     for (int i = 0; i < rows; i++)
1857     {
1858       List<Double> dVector = mData.getRow().get(i).getV();
1859       vals[i] = new double[dVector.size()];
1860       int dvi = 0;
1861       for (Double d : dVector)
1862       {
1863         vals[i][dvi++] = d;
1864       }
1865     }
1866
1867     MatrixI m = new Matrix(vals);
1868
1869     if (mData.getD() != null)
1870     {
1871       List<Double> dVector = mData.getD().getV();
1872       double[] vec = new double[dVector.size()];
1873       int dvi = 0;
1874       for (Double d : dVector)
1875       {
1876         vec[dvi++] = d;
1877       }
1878       m.setD(vec);
1879     }
1880     if (mData.getE() != null)
1881     {
1882       List<Double> dVector = mData.getE().getV();
1883       double[] vec = new double[dVector.size()];
1884       int dvi = 0;
1885       for (Double d : dVector)
1886       {
1887         vec[dvi++] = d;
1888       }
1889       m.setE(vec);
1890     }
1891
1892     return m;
1893   }
1894
1895   /**
1896    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1897    * for each viewer, with
1898    * <ul>
1899    * <li>viewer geometry (position, size, split pane divider location)</li>
1900    * <li>index of the selected structure in the viewer (currently shows gapped
1901    * or ungapped)</li>
1902    * <li>the id of the annotation holding RNA secondary structure</li>
1903    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1904    * </ul>
1905    * Varna viewer state is also written out (in native Varna XML) to separate
1906    * project jar entries. A separate entry is written for each RNA structure
1907    * displayed, with the naming convention
1908    * <ul>
1909    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1910    * </ul>
1911    * 
1912    * @param jout
1913    * @param jseq
1914    * @param jds
1915    * @param viewIds
1916    * @param ap
1917    * @param storeDataset
1918    */
1919   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1920           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1921           boolean storeDataset)
1922   {
1923     if (Desktop.getDesktopPane() == null)
1924     {
1925       return;
1926     }
1927     JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
1928     for (int f = frames.length - 1; f > -1; f--)
1929     {
1930       if (frames[f] instanceof AppVarna)
1931       {
1932         AppVarna varna = (AppVarna) frames[f];
1933         /*
1934          * link the sequence to every viewer that is showing it and is linked to
1935          * its alignment panel
1936          */
1937         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1938         {
1939           String viewId = varna.getViewId();
1940           RnaViewer rna = new RnaViewer();
1941           rna.setViewId(viewId);
1942           rna.setTitle(varna.getTitle());
1943           rna.setXpos(varna.getX());
1944           rna.setYpos(varna.getY());
1945           rna.setWidth(varna.getWidth());
1946           rna.setHeight(varna.getHeight());
1947           rna.setDividerLocation(varna.getDividerLocation());
1948           rna.setSelectedRna(varna.getSelectedIndex());
1949           // jseq.addRnaViewer(rna);
1950           jseq.getRnaViewer().add(rna);
1951
1952           /*
1953            * Store each Varna panel's state once in the project per sequence.
1954            * First time through only (storeDataset==false)
1955            */
1956           // boolean storeSessions = false;
1957           // String sequenceViewId = viewId + seqsToIds.get(jds);
1958           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1959           // {
1960           // viewIds.add(sequenceViewId);
1961           // storeSessions = true;
1962           // }
1963           for (RnaModel model : varna.getModels())
1964           {
1965             if (model.seq == jds)
1966             {
1967               /*
1968                * VARNA saves each view (sequence or alignment secondary
1969                * structure, gapped or trimmed) as a separate XML file
1970                */
1971               String jarEntryName = rnaSessions.get(model);
1972               if (jarEntryName == null)
1973               {
1974
1975                 String varnaStateFile = varna.getStateInfo(model.rna);
1976                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1977                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1978                 rnaSessions.put(model, jarEntryName);
1979               }
1980               SecondaryStructure ss = new SecondaryStructure();
1981               String annotationId = varna.getAnnotation(jds).annotationId;
1982               ss.setAnnotationId(annotationId);
1983               ss.setViewerState(jarEntryName);
1984               ss.setGapped(model.gapped);
1985               ss.setTitle(model.title);
1986               // rna.addSecondaryStructure(ss);
1987               rna.getSecondaryStructure().add(ss);
1988             }
1989           }
1990         }
1991       }
1992     }
1993   }
1994
1995   /**
1996    * Copy the contents of a file to a new entry added to the output jar
1997    * 
1998    * @param jout
1999    * @param infilePath
2000    * @param jarEntryName
2001    */
2002   protected void copyFileToJar(JarOutputStream jout, String infilePath,
2003           String jarEntryName)
2004   {
2005     DataInputStream dis = null;
2006     try
2007     {
2008       File file = new File(infilePath);
2009       if (file.exists() && jout != null)
2010       {
2011         dis = new DataInputStream(new FileInputStream(file));
2012         byte[] data = new byte[(int) file.length()];
2013         dis.readFully(data);
2014         writeJarEntry(jout, jarEntryName, data);
2015       }
2016     } catch (Exception ex)
2017     {
2018       ex.printStackTrace();
2019     } finally
2020     {
2021       if (dis != null)
2022       {
2023         try
2024         {
2025           dis.close();
2026         } catch (IOException e)
2027         {
2028           // ignore
2029         }
2030       }
2031     }
2032   }
2033
2034   /**
2035    * Write the data to a new entry of given name in the output jar file
2036    * 
2037    * @param jout
2038    * @param jarEntryName
2039    * @param data
2040    * @throws IOException
2041    */
2042   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
2043           byte[] data) throws IOException
2044   {
2045     if (jout != null)
2046     {
2047       jarEntryName = jarEntryName.replace('\\','/');
2048       System.out.println("Writing jar entry " + jarEntryName);
2049       jout.putNextEntry(new JarEntry(jarEntryName));
2050       DataOutputStream dout = new DataOutputStream(jout);
2051       dout.write(data, 0, data.length);
2052       dout.flush();
2053       jout.closeEntry();
2054     }
2055   }
2056
2057   /**
2058    * Save the state of a structure viewer
2059    * 
2060    * @param ap
2061    * @param jds
2062    * @param pdb
2063    *          the archive XML element under which to save the state
2064    * @param entry
2065    * @param viewIds
2066    * @param matchedFile
2067    * @param viewFrame
2068    * @return
2069    */
2070   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
2071           Pdbids pdb, PDBEntry entry, List<String> viewIds,
2072           String matchedFile, StructureViewerBase viewFrame)
2073   {
2074     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
2075
2076     /*
2077      * Look for any bindings for this viewer to the PDB file of interest
2078      * (including part matches excluding chain id)
2079      */
2080     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
2081     {
2082       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
2083       final String pdbId = pdbentry.getId();
2084       if (!pdbId.equals(entry.getId())
2085               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
2086                       .startsWith(pdbId.toLowerCase())))
2087       {
2088         /*
2089          * not interested in a binding to a different PDB entry here
2090          */
2091         continue;
2092       }
2093       if (matchedFile == null)
2094       {
2095         matchedFile = pdbentry.getFile();
2096       }
2097       else if (!matchedFile.equals(pdbentry.getFile()))
2098       {
2099         Cache.log.warn(
2100                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
2101                         + pdbentry.getFile());
2102       }
2103       // record the
2104       // file so we
2105       // can get at it if the ID
2106       // match is ambiguous (e.g.
2107       // 1QIP==1qipA)
2108
2109       for (int smap = 0; smap < viewFrame.getBinding()
2110               .getSequence()[peid].length; smap++)
2111       {
2112         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
2113         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
2114         {
2115           StructureState state = new StructureState();
2116           state.setVisible(true);
2117           state.setXpos(viewFrame.getX());
2118           state.setYpos(viewFrame.getY());
2119           state.setWidth(viewFrame.getWidth());
2120           state.setHeight(viewFrame.getHeight());
2121           final String viewId = viewFrame.getViewId();
2122           state.setViewId(viewId);
2123           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
2124           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
2125           state.setColourByJmol(viewFrame.isColouredByViewer());
2126           state.setType(viewFrame.getViewerType().toString());
2127           // pdb.addStructureState(state);
2128           pdb.getStructureState().add(state);
2129         }
2130       }
2131     }
2132     return matchedFile;
2133   }
2134
2135   /**
2136    * Populates the AnnotationColourScheme xml for save. This captures the
2137    * settings of the options in the 'Colour by Annotation' dialog.
2138    * 
2139    * @param acg
2140    * @param userColours
2141    * @param jm
2142    * @return
2143    */
2144   private AnnotationColourScheme constructAnnotationColours(
2145           AnnotationColourGradient acg, List<UserColourScheme> userColours,
2146           JalviewModel jm)
2147   {
2148     AnnotationColourScheme ac = new AnnotationColourScheme();
2149     ac.setAboveThreshold(acg.getAboveThreshold());
2150     ac.setThreshold(acg.getAnnotationThreshold());
2151     // 2.10.2 save annotationId (unique) not annotation label
2152     ac.setAnnotation(acg.getAnnotation().annotationId);
2153     if (acg.getBaseColour() instanceof UserColourScheme)
2154     {
2155       ac.setColourScheme(
2156               setUserColourScheme(acg.getBaseColour(), userColours, jm));
2157     }
2158     else
2159     {
2160       ac.setColourScheme(
2161               ColourSchemeProperty.getColourName(acg.getBaseColour()));
2162     }
2163
2164     ac.setMaxColour(acg.getMaxColour().getRGB());
2165     ac.setMinColour(acg.getMinColour().getRGB());
2166     ac.setPerSequence(acg.isSeqAssociated());
2167     ac.setPredefinedColours(acg.isPredefinedColours());
2168     return ac;
2169   }
2170
2171   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
2172           IdentityHashMap<SequenceGroup, String> groupRefs,
2173           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
2174           SequenceSet vamsasSet)
2175   {
2176
2177     for (int i = 0; i < aa.length; i++)
2178     {
2179       Annotation an = new Annotation();
2180
2181       AlignmentAnnotation annotation = aa[i];
2182       if (annotation.annotationId != null)
2183       {
2184         annotationIds.put(annotation.annotationId, annotation);
2185       }
2186
2187       an.setId(annotation.annotationId);
2188
2189       an.setVisible(annotation.visible);
2190
2191       an.setDescription(annotation.description);
2192
2193       if (annotation.sequenceRef != null)
2194       {
2195         // 2.9 JAL-1781 xref on sequence id rather than name
2196         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
2197       }
2198       if (annotation.groupRef != null)
2199       {
2200         String groupIdr = groupRefs.get(annotation.groupRef);
2201         if (groupIdr == null)
2202         {
2203           // make a locally unique String
2204           groupRefs.put(annotation.groupRef,
2205                   groupIdr = ("" + System.currentTimeMillis()
2206                           + annotation.groupRef.getName()
2207                           + groupRefs.size()));
2208         }
2209         an.setGroupRef(groupIdr.toString());
2210       }
2211
2212       // store all visualization attributes for annotation
2213       an.setGraphHeight(annotation.graphHeight);
2214       an.setCentreColLabels(annotation.centreColLabels);
2215       an.setScaleColLabels(annotation.scaleColLabel);
2216       an.setShowAllColLabels(annotation.showAllColLabels);
2217       an.setBelowAlignment(annotation.belowAlignment);
2218
2219       if (annotation.graph > 0)
2220       {
2221         an.setGraph(true);
2222         an.setGraphType(annotation.graph);
2223         an.setGraphGroup(annotation.graphGroup);
2224         if (annotation.getThreshold() != null)
2225         {
2226           ThresholdLine line = new ThresholdLine();
2227           line.setLabel(annotation.getThreshold().label);
2228           line.setValue(annotation.getThreshold().value);
2229           line.setColour(annotation.getThreshold().colour.getRGB());
2230           an.setThresholdLine(line);
2231         }
2232       }
2233       else
2234       {
2235         an.setGraph(false);
2236       }
2237
2238       an.setLabel(annotation.label);
2239
2240       if (annotation == av.getAlignmentQualityAnnot()
2241               || annotation == av.getAlignmentConservationAnnotation()
2242               || annotation == av.getAlignmentConsensusAnnotation()
2243               || annotation.autoCalculated)
2244       {
2245         // new way of indicating autocalculated annotation -
2246         an.setAutoCalculated(annotation.autoCalculated);
2247       }
2248       if (annotation.hasScore())
2249       {
2250         an.setScore(annotation.getScore());
2251       }
2252
2253       if (annotation.getCalcId() != null)
2254       {
2255         calcIdSet.add(annotation.getCalcId());
2256         an.setCalcId(annotation.getCalcId());
2257       }
2258       if (annotation.hasProperties())
2259       {
2260         for (String pr : annotation.getProperties())
2261         {
2262           jalview.xml.binding.jalview.Annotation.Property prop = new jalview.xml.binding.jalview.Annotation.Property();
2263           prop.setName(pr);
2264           prop.setValue(annotation.getProperty(pr));
2265           // an.addProperty(prop);
2266           an.getProperty().add(prop);
2267         }
2268       }
2269
2270       AnnotationElement ae;
2271       if (annotation.annotations != null)
2272       {
2273         an.setScoreOnly(false);
2274         for (int a = 0; a < annotation.annotations.length; a++)
2275         {
2276           if ((annotation == null) || (annotation.annotations[a] == null))
2277           {
2278             continue;
2279           }
2280
2281           ae = new AnnotationElement();
2282           if (annotation.annotations[a].description != null)
2283           {
2284             ae.setDescription(annotation.annotations[a].description);
2285           }
2286           if (annotation.annotations[a].displayCharacter != null)
2287           {
2288             ae.setDisplayCharacter(
2289                     annotation.annotations[a].displayCharacter);
2290           }
2291
2292           if (!Float.isNaN(annotation.annotations[a].value))
2293           {
2294             ae.setValue(annotation.annotations[a].value);
2295           }
2296
2297           ae.setPosition(a);
2298           if (annotation.annotations[a].secondaryStructure > ' ')
2299           {
2300             ae.setSecondaryStructure(
2301                     annotation.annotations[a].secondaryStructure + "");
2302           }
2303
2304           if (annotation.annotations[a].colour != null
2305                   && annotation.annotations[a].colour != java.awt.Color.black)
2306           {
2307             ae.setColour(annotation.annotations[a].colour.getRGB());
2308           }
2309
2310           // an.addAnnotationElement(ae);
2311           an.getAnnotationElement().add(ae);
2312           if (annotation.autoCalculated)
2313           {
2314             // only write one non-null entry into the annotation row -
2315             // sufficient to get the visualization attributes necessary to
2316             // display data
2317             continue;
2318           }
2319         }
2320       }
2321       else
2322       {
2323         an.setScoreOnly(true);
2324       }
2325       if (!storeDS || (storeDS && !annotation.autoCalculated))
2326       {
2327         // skip autocalculated annotation - these are only provided for
2328         // alignments
2329         // vamsasSet.addAnnotation(an);
2330         vamsasSet.getAnnotation().add(an);
2331       }
2332     }
2333
2334   }
2335
2336   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
2337   {
2338     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
2339     if (settings != null)
2340     {
2341       CalcIdParam vCalcIdParam = new CalcIdParam();
2342       vCalcIdParam.setCalcId(calcId);
2343       // vCalcIdParam.addServiceURL(settings.getServiceURI());
2344       vCalcIdParam.getServiceURL().add(settings.getServiceURI());
2345       // generic URI allowing a third party to resolve another instance of the
2346       // service used for this calculation
2347       for (String url : settings.getServiceURLs())
2348       {
2349         // vCalcIdParam.addServiceURL(urls);
2350         vCalcIdParam.getServiceURL().add(url);
2351       }
2352       vCalcIdParam.setVersion("1.0");
2353       if (settings.getPreset() != null)
2354       {
2355         WsParamSetI setting = settings.getPreset();
2356         vCalcIdParam.setName(setting.getName());
2357         vCalcIdParam.setDescription(setting.getDescription());
2358       }
2359       else
2360       {
2361         vCalcIdParam.setName("");
2362         vCalcIdParam.setDescription("Last used parameters");
2363       }
2364       // need to be able to recover 1) settings 2) user-defined presets or
2365       // recreate settings from preset 3) predefined settings provided by
2366       // service - or settings that can be transferred (or discarded)
2367       vCalcIdParam.setParameters(
2368               settings.getWsParamFile().replace("\n", "|\\n|"));
2369       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
2370       // todo - decide if updateImmediately is needed for any projects.
2371
2372       return vCalcIdParam;
2373     }
2374     return null;
2375   }
2376
2377   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
2378           AlignViewport av)
2379   {
2380     if (calcIdParam.getVersion().equals("1.0"))
2381     {
2382       final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
2383       Jws2Instance service = Jws2Discoverer.getDiscoverer()
2384               .getPreferredServiceFor(calcIds);
2385       if (service != null)
2386       {
2387         WsParamSetI parmSet = null;
2388         try
2389         {
2390           parmSet = service.getParamStore().parseServiceParameterFile(
2391                   calcIdParam.getName(), calcIdParam.getDescription(),
2392                   calcIds,
2393                   calcIdParam.getParameters().replace("|\\n|", "\n"));
2394         } catch (IOException x)
2395         {
2396           warn("Couldn't parse parameter data for "
2397                   + calcIdParam.getCalcId(), x);
2398           return false;
2399         }
2400         List<ArgumentI> argList = null;
2401         if (calcIdParam.getName().length() > 0)
2402         {
2403           parmSet = service.getParamStore()
2404                   .getPreset(calcIdParam.getName());
2405           if (parmSet != null)
2406           {
2407             // TODO : check we have a good match with settings in AACon -
2408             // otherwise we'll need to create a new preset
2409           }
2410         }
2411         else
2412         {
2413           argList = parmSet.getArguments();
2414           parmSet = null;
2415         }
2416         AAConSettings settings = new AAConSettings(
2417                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
2418         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
2419                 calcIdParam.isNeedsUpdate());
2420         return true;
2421       }
2422       else
2423       {
2424         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
2425         return false;
2426       }
2427     }
2428     throw new Error(MessageManager.formatMessage(
2429             "error.unsupported_version_calcIdparam", new Object[]
2430             { calcIdParam.toString() }));
2431   }
2432
2433   /**
2434    * External mapping between jalview objects and objects yielding a valid and
2435    * unique object ID string. This is null for normal Jalview project IO, but
2436    * non-null when a jalview project is being read or written as part of a
2437    * vamsas session.
2438    */
2439   IdentityHashMap jv2vobj = null;
2440
2441   /**
2442    * Construct a unique ID for jvobj using either existing bindings or if none
2443    * exist, the result of the hashcode call for the object.
2444    * 
2445    * @param jvobj
2446    *          jalview data object
2447    * @return unique ID for referring to jvobj
2448    */
2449   private String makeHashCode(Object jvobj, String altCode)
2450   {
2451     if (jv2vobj != null)
2452     {
2453       Object id = jv2vobj.get(jvobj);
2454       if (id != null)
2455       {
2456         return id.toString();
2457       }
2458       // check string ID mappings
2459       if (jvids2vobj != null && jvobj instanceof String)
2460       {
2461         id = jvids2vobj.get(jvobj);
2462       }
2463       if (id != null)
2464       {
2465         return id.toString();
2466       }
2467       // give up and warn that something has gone wrong
2468       warn("Cannot find ID for object in external mapping : " + jvobj);
2469     }
2470     return altCode;
2471   }
2472
2473   /**
2474    * return local jalview object mapped to ID, if it exists
2475    * 
2476    * @param idcode
2477    *          (may be null)
2478    * @return null or object bound to idcode
2479    */
2480   private Object retrieveExistingObj(String idcode)
2481   {
2482     if (idcode != null && vobj2jv != null)
2483     {
2484       return vobj2jv.get(idcode);
2485     }
2486     return null;
2487   }
2488
2489   /**
2490    * binding from ID strings from external mapping table to jalview data model
2491    * objects.
2492    */
2493   private Hashtable vobj2jv;
2494
2495   private Sequence createVamsasSequence(String id, SequenceI jds)
2496   {
2497     return createVamsasSequence(true, id, jds, null);
2498   }
2499
2500   private Sequence createVamsasSequence(boolean recurse, String id,
2501           SequenceI jds, SequenceI parentseq)
2502   {
2503     Sequence vamsasSeq = new Sequence();
2504     vamsasSeq.setId(id);
2505     vamsasSeq.setName(jds.getName());
2506     vamsasSeq.setSequence(jds.getSequenceAsString());
2507     vamsasSeq.setDescription(jds.getDescription());
2508     List<DBRefEntry> dbrefs = null;
2509     if (jds.getDatasetSequence() != null)
2510     {
2511       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
2512     }
2513     else
2514     {
2515       // seqId==dsseqid so we can tell which sequences really are
2516       // dataset sequences only
2517       vamsasSeq.setDsseqid(id);
2518       dbrefs = jds.getDBRefs();
2519       if (parentseq == null)
2520       {
2521         parentseq = jds;
2522       }
2523     }
2524     if (dbrefs != null)
2525     {
2526       for (int d = 0, nd = dbrefs.size(); d < nd; d++)
2527       {
2528         DBRef dbref = new DBRef();
2529         DBRefEntry ref = dbrefs.get(d);
2530         dbref.setSource(ref.getSource());
2531         dbref.setVersion(ref.getVersion());
2532         dbref.setAccessionId(ref.getAccessionId());
2533         if (ref.hasMap())
2534         {
2535           Mapping mp = createVamsasMapping(ref.getMap(), parentseq,
2536                   jds, recurse);
2537           dbref.setMapping(mp);
2538         }
2539         // vamsasSeq.addDBRef(dbref);
2540         vamsasSeq.getDBRef().add(dbref);
2541       }
2542     }
2543     return vamsasSeq;
2544   }
2545
2546   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
2547           SequenceI parentseq, SequenceI jds, boolean recurse)
2548   {
2549     Mapping mp = null;
2550     if (jmp.getMap() != null)
2551     {
2552       mp = new Mapping();
2553
2554       jalview.util.MapList mlst = jmp.getMap();
2555       List<int[]> r = mlst.getFromRanges();
2556       for (int[] range : r)
2557       {
2558         MapListFrom mfrom = new MapListFrom();
2559         mfrom.setStart(range[0]);
2560         mfrom.setEnd(range[1]);
2561         // mp.addMapListFrom(mfrom);
2562         mp.getMapListFrom().add(mfrom);
2563       }
2564       r = mlst.getToRanges();
2565       for (int[] range : r)
2566       {
2567         MapListTo mto = new MapListTo();
2568         mto.setStart(range[0]);
2569         mto.setEnd(range[1]);
2570         // mp.addMapListTo(mto);
2571         mp.getMapListTo().add(mto);
2572       }
2573       mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
2574       mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
2575       if (jmp.getTo() != null)
2576       {
2577         // MappingChoice mpc = new MappingChoice();
2578
2579         // check/create ID for the sequence referenced by getTo()
2580
2581         String jmpid = "";
2582         SequenceI ps = null;
2583         if (parentseq != jmp.getTo()
2584                 && parentseq.getDatasetSequence() != jmp.getTo())
2585         {
2586           // chaining dbref rather than a handshaking one
2587           jmpid = seqHash(ps = jmp.getTo());
2588         }
2589         else
2590         {
2591           jmpid = seqHash(ps = parentseq);
2592         }
2593         // mpc.setDseqFor(jmpid);
2594         mp.setDseqFor(jmpid);
2595         if (!seqRefIds.containsKey(jmpid))
2596         {
2597           jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2598           seqRefIds.put(jmpid, ps);
2599         }
2600         else
2601         {
2602           jalview.bin.Cache.log.debug("reusing DseqFor ID");
2603         }
2604
2605         // mp.setMappingChoice(mpc);
2606       }
2607     }
2608     return mp;
2609   }
2610
2611   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2612           List<UserColourScheme> userColours, JalviewModel jm)
2613   {
2614     String id = null;
2615     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2616     boolean newucs = false;
2617     if (!userColours.contains(ucs))
2618     {
2619       userColours.add(ucs);
2620       newucs = true;
2621     }
2622     id = "ucs" + userColours.indexOf(ucs);
2623     if (newucs)
2624     {
2625       // actually create the scheme's entry in the XML model
2626       java.awt.Color[] colours = ucs.getColours();
2627       UserColours uc = new UserColours();
2628       // UserColourScheme jbucs = new UserColourScheme();
2629       JalviewUserColours jbucs = new JalviewUserColours();
2630
2631       for (int i = 0; i < colours.length; i++)
2632       {
2633         Colour col = new Colour();
2634         col.setName(ResidueProperties.aa[i]);
2635         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2636         // jbucs.addColour(col);
2637         jbucs.getColour().add(col);
2638       }
2639       if (ucs.getLowerCaseColours() != null)
2640       {
2641         colours = ucs.getLowerCaseColours();
2642         for (int i = 0; i < colours.length; i++)
2643         {
2644           Colour col = new Colour();
2645           col.setName(ResidueProperties.aa[i].toLowerCase());
2646           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2647           // jbucs.addColour(col);
2648           jbucs.getColour().add(col);
2649         }
2650       }
2651
2652       uc.setId(id);
2653       uc.setUserColourScheme(jbucs);
2654       // jm.addUserColours(uc);
2655       jm.getUserColours().add(uc);
2656     }
2657
2658     return id;
2659   }
2660
2661   jalview.schemes.UserColourScheme getUserColourScheme(
2662           JalviewModel jm, String id)
2663   {
2664     List<UserColours> uc = jm.getUserColours();
2665     UserColours colours = null;
2666 /*
2667     for (int i = 0; i < uc.length; i++)
2668     {
2669       if (uc[i].getId().equals(id))
2670       {
2671         colours = uc[i];
2672         break;
2673       }
2674     }
2675 */
2676     for (UserColours c : uc)
2677     {
2678       if (c.getId().equals(id))
2679       {
2680         colours = c;
2681         break;
2682       }
2683     }
2684
2685     java.awt.Color[] newColours = new java.awt.Color[24];
2686
2687     for (int i = 0; i < 24; i++)
2688     {
2689       newColours[i] = new java.awt.Color(Integer.parseInt(
2690               // colours.getUserColourScheme().getColour(i).getRGB(), 16));
2691               colours.getUserColourScheme().getColour().get(i).getRGB(),
2692               16));
2693     }
2694
2695     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2696             newColours);
2697
2698     if (colours.getUserColourScheme().getColour().size()/*Count()*/ > 24)
2699     {
2700       newColours = new java.awt.Color[23];
2701       for (int i = 0; i < 23; i++)
2702       {
2703         newColours[i] = new java.awt.Color(Integer.parseInt(
2704                 colours.getUserColourScheme().getColour().get(i + 24)
2705                         .getRGB(),
2706                 16));
2707       }
2708       ucs.setLowerCaseColours(newColours);
2709     }
2710
2711     return ucs;
2712   }
2713
2714   /**
2715    * contains last error message (if any) encountered by XML loader.
2716    */
2717   String errorMessage = null;
2718
2719   /**
2720    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2721    * exceptions are raised during project XML parsing
2722    */
2723   public boolean attemptversion1parse = false;
2724
2725   /**
2726    * Load a jalview project archive from a jar file
2727    * 
2728    * @param file
2729    *          - HTTP URL or filename
2730    */
2731   public AlignFrame loadJalviewAlign(final Object file)
2732   {
2733
2734     jalview.gui.AlignFrame af = null;
2735
2736     try
2737     {
2738       // create list to store references for any new Jmol viewers created
2739       newStructureViewers = new Vector<>();
2740       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2741       // Workaround is to make sure caller implements the JarInputStreamProvider
2742       // interface
2743       // so we can re-open the jar input stream for each entry.
2744
2745       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2746       af = loadJalviewAlign(jprovider);
2747       if (af != null)
2748       {
2749         af.setMenusForViewport();
2750       }
2751     } catch (MalformedURLException e)
2752     {
2753       errorMessage = "Invalid URL format for '" + file + "'";
2754       reportErrors();
2755     } finally
2756     {
2757       try
2758       {
2759         SwingUtilities.invokeAndWait(new Runnable()
2760         {
2761           @Override
2762           public void run()
2763           {
2764             setLoadingFinishedForNewStructureViewers();
2765           }
2766         });
2767       } catch (Exception x)
2768       {
2769         System.err.println("Error loading alignment: " + x.getMessage());
2770       }
2771     }
2772     return af;
2773   }
2774
2775         @SuppressWarnings("unused")
2776         private jarInputStreamProvider createjarInputStreamProvider(final Object ofile) throws MalformedURLException {
2777
2778                 // BH 2018 allow for bytes already attached to File object
2779                 try {
2780                         String file = (ofile instanceof File ? ((File) ofile).getCanonicalPath() : ofile.toString());
2781       byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
2782               : null;
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 (Instance.getDesktop() != null)
2920       {
2921         Instance.getDesktop().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       Instance.getDesktop().gatherViews(fr);
2950     }
2951
2952     restoreSplitFrames();
2953     for (AlignmentI ds : importedDatasets.keySet())
2954     {
2955       if (ds.getCodonFrames() != null)
2956       {
2957         StructureSelectionManager
2958                 .getStructureSelectionManager(Instance.getDesktop())
2959                 .registerMappings(ds.getCodonFrames());
2960       }
2961     }
2962     if (errorMessage != null)
2963     {
2964       reportErrors();
2965     }
2966
2967     if (Instance.getDesktop() != null)
2968     {
2969       Instance.getDesktop().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       Instance.getDesktop().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.getDesktopPane(),
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(Instance.getDesktop())
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.getDesktop().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.getDesktopPane().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 }