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