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