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