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