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