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