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