9f796f2a4a80e2d9090b5167e5c2e8eb1c22a79b
[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     List<Double> dVector;
1826     double[] vec;
1827
1828     for (int i = 0; i < rows; i++)
1829     {
1830       dVector = mData.getRow().get(i).getV();
1831       vals[i] = new double[dVector.size()];
1832       int dvi = 0;
1833       for (Double d : dVector)
1834       {
1835         vals[i][dvi++] = d;
1836       }
1837     }
1838
1839     MatrixI m = new Matrix(vals);
1840
1841     if (mData.getD() != null)
1842     {
1843       dVector = mData.getD().getV();
1844       vec = new double[dVector.size()];
1845       int dvi = 0;
1846       for (Double d : dVector)
1847       {
1848         vec[dvi++] = d;
1849       }
1850       m.setD(vec);
1851     }
1852     if (mData.getE() != null)
1853     {
1854       dVector = mData.getE().getV();
1855       vec = new double[dVector.size()];
1856       int dvi = 0;
1857       for (Double d : dVector)
1858       {
1859         vec[dvi++] = d;
1860       }
1861       m.setE(vec);
1862     }
1863
1864     return m;
1865   }
1866
1867   /**
1868    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1869    * for each viewer, with
1870    * <ul>
1871    * <li>viewer geometry (position, size, split pane divider location)</li>
1872    * <li>index of the selected structure in the viewer (currently shows gapped
1873    * or ungapped)</li>
1874    * <li>the id of the annotation holding RNA secondary structure</li>
1875    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1876    * </ul>
1877    * Varna viewer state is also written out (in native Varna XML) to separate
1878    * project jar entries. A separate entry is written for each RNA structure
1879    * displayed, with the naming convention
1880    * <ul>
1881    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1882    * </ul>
1883    * 
1884    * @param jout
1885    * @param jseq
1886    * @param jds
1887    * @param viewIds
1888    * @param ap
1889    * @param storeDataset
1890    */
1891   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1892           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1893           boolean storeDataset)
1894   {
1895     if (Desktop.desktop == null)
1896     {
1897       return;
1898     }
1899     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1900     for (int f = frames.length - 1; f > -1; f--)
1901     {
1902       if (frames[f] instanceof AppVarna)
1903       {
1904         AppVarna varna = (AppVarna) frames[f];
1905         /*
1906          * link the sequence to every viewer that is showing it and is linked to
1907          * its alignment panel
1908          */
1909         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1910         {
1911           String viewId = varna.getViewId();
1912           RnaViewer rna = new RnaViewer();
1913           rna.setViewId(viewId);
1914           rna.setTitle(varna.getTitle());
1915           rna.setXpos(varna.getX());
1916           rna.setYpos(varna.getY());
1917           rna.setWidth(varna.getWidth());
1918           rna.setHeight(varna.getHeight());
1919           rna.setDividerLocation(varna.getDividerLocation());
1920           rna.setSelectedRna(varna.getSelectedIndex());
1921           // jseq.addRnaViewer(rna);
1922           jseq.getRnaViewer().add(rna);
1923
1924           /*
1925            * Store each Varna panel's state once in the project per sequence.
1926            * First time through only (storeDataset==false)
1927            */
1928           // boolean storeSessions = false;
1929           // String sequenceViewId = viewId + seqsToIds.get(jds);
1930           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1931           // {
1932           // viewIds.add(sequenceViewId);
1933           // storeSessions = true;
1934           // }
1935           for (RnaModel model : varna.getModels())
1936           {
1937             if (model.seq == jds)
1938             {
1939               /*
1940                * VARNA saves each view (sequence or alignment secondary
1941                * structure, gapped or trimmed) as a separate XML file
1942                */
1943               String jarEntryName = rnaSessions.get(model);
1944               if (jarEntryName == null)
1945               {
1946
1947                 String varnaStateFile = varna.getStateInfo(model.rna);
1948                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1949                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1950                 rnaSessions.put(model, jarEntryName);
1951               }
1952               SecondaryStructure ss = new SecondaryStructure();
1953               String annotationId = varna.getAnnotation(jds).annotationId;
1954               ss.setAnnotationId(annotationId);
1955               ss.setViewerState(jarEntryName);
1956               ss.setGapped(model.gapped);
1957               ss.setTitle(model.title);
1958               // rna.addSecondaryStructure(ss);
1959               rna.getSecondaryStructure().add(ss);
1960             }
1961           }
1962         }
1963       }
1964     }
1965   }
1966
1967   /**
1968    * Copy the contents of a file to a new entry added to the output jar
1969    * 
1970    * @param jout
1971    * @param infilePath
1972    * @param jarEntryName
1973    */
1974   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1975           String jarEntryName)
1976   {
1977     DataInputStream dis = null;
1978     try
1979     {
1980       File file = new File(infilePath);
1981       if (file.exists() && jout != null)
1982       {
1983         dis = new DataInputStream(new FileInputStream(file));
1984         byte[] data = new byte[(int) file.length()];
1985         dis.readFully(data);
1986         writeJarEntry(jout, jarEntryName, data);
1987       }
1988     } catch (Exception ex)
1989     {
1990       ex.printStackTrace();
1991     } finally
1992     {
1993       if (dis != null)
1994       {
1995         try
1996         {
1997           dis.close();
1998         } catch (IOException e)
1999         {
2000           // ignore
2001         }
2002       }
2003     }
2004   }
2005
2006   /**
2007    * Write the data to a new entry of given name in the output jar file
2008    * 
2009    * @param jout
2010    * @param jarEntryName
2011    * @param data
2012    * @throws IOException
2013    */
2014   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
2015           byte[] data) throws IOException
2016   {
2017     if (jout != null)
2018     {
2019       System.out.println("Writing jar entry " + jarEntryName);
2020       jout.putNextEntry(new JarEntry(jarEntryName));
2021       DataOutputStream dout = new DataOutputStream(jout);
2022       dout.write(data, 0, data.length);
2023       dout.flush();
2024       jout.closeEntry();
2025     }
2026   }
2027
2028   /**
2029    * Save the state of a structure viewer
2030    * 
2031    * @param ap
2032    * @param jds
2033    * @param pdb
2034    *          the archive XML element under which to save the state
2035    * @param entry
2036    * @param viewIds
2037    * @param matchedFile
2038    * @param viewFrame
2039    * @return
2040    */
2041   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
2042           Pdbids pdb, PDBEntry entry, List<String> viewIds,
2043           String matchedFile, StructureViewerBase viewFrame)
2044   {
2045     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
2046
2047     /*
2048      * Look for any bindings for this viewer to the PDB file of interest
2049      * (including part matches excluding chain id)
2050      */
2051     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
2052     {
2053       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
2054       final String pdbId = pdbentry.getId();
2055       if (!pdbId.equals(entry.getId())
2056               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
2057                       .startsWith(pdbId.toLowerCase())))
2058       {
2059         /*
2060          * not interested in a binding to a different PDB entry here
2061          */
2062         continue;
2063       }
2064       if (matchedFile == null)
2065       {
2066         matchedFile = pdbentry.getFile();
2067       }
2068       else if (!matchedFile.equals(pdbentry.getFile()))
2069       {
2070         Cache.log.warn(
2071                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
2072                         + pdbentry.getFile());
2073       }
2074       // record the
2075       // file so we
2076       // can get at it if the ID
2077       // match is ambiguous (e.g.
2078       // 1QIP==1qipA)
2079
2080       for (int smap = 0; smap < viewFrame.getBinding()
2081               .getSequence()[peid].length; smap++)
2082       {
2083         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
2084         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
2085         {
2086           StructureState state = new StructureState();
2087           state.setVisible(true);
2088           state.setXpos(viewFrame.getX());
2089           state.setYpos(viewFrame.getY());
2090           state.setWidth(viewFrame.getWidth());
2091           state.setHeight(viewFrame.getHeight());
2092           final String viewId = viewFrame.getViewId();
2093           state.setViewId(viewId);
2094           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
2095           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
2096           state.setColourByJmol(viewFrame.isColouredByViewer());
2097           state.setType(viewFrame.getViewerType().toString());
2098           // pdb.addStructureState(state);
2099           pdb.getStructureState().add(state);
2100         }
2101       }
2102     }
2103     return matchedFile;
2104   }
2105
2106   /**
2107    * Populates the AnnotationColourScheme xml for save. This captures the
2108    * settings of the options in the 'Colour by Annotation' dialog.
2109    * 
2110    * @param acg
2111    * @param userColours
2112    * @param jm
2113    * @return
2114    */
2115   private AnnotationColourScheme constructAnnotationColours(
2116           AnnotationColourGradient acg, List<UserColourScheme> userColours,
2117           JalviewModel jm)
2118   {
2119     AnnotationColourScheme ac = new AnnotationColourScheme();
2120     ac.setAboveThreshold(acg.getAboveThreshold());
2121     ac.setThreshold(acg.getAnnotationThreshold());
2122     // 2.10.2 save annotationId (unique) not annotation label
2123     ac.setAnnotation(acg.getAnnotation().annotationId);
2124     if (acg.getBaseColour() instanceof UserColourScheme)
2125     {
2126       ac.setColourScheme(
2127               setUserColourScheme(acg.getBaseColour(), userColours, jm));
2128     }
2129     else
2130     {
2131       ac.setColourScheme(
2132               ColourSchemeProperty.getColourName(acg.getBaseColour()));
2133     }
2134
2135     ac.setMaxColour(acg.getMaxColour().getRGB());
2136     ac.setMinColour(acg.getMinColour().getRGB());
2137     ac.setPerSequence(acg.isSeqAssociated());
2138     ac.setPredefinedColours(acg.isPredefinedColours());
2139     return ac;
2140   }
2141
2142   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
2143           IdentityHashMap<SequenceGroup, String> groupRefs,
2144           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
2145           SequenceSet vamsasSet)
2146   {
2147
2148     for (int i = 0; i < aa.length; i++)
2149     {
2150       Annotation an = new Annotation();
2151
2152       AlignmentAnnotation annotation = aa[i];
2153       if (annotation.annotationId != null)
2154       {
2155         annotationIds.put(annotation.annotationId, annotation);
2156       }
2157
2158       an.setId(annotation.annotationId);
2159
2160       an.setVisible(annotation.visible);
2161
2162       an.setDescription(annotation.description);
2163
2164       if (annotation.sequenceRef != null)
2165       {
2166         // 2.9 JAL-1781 xref on sequence id rather than name
2167         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
2168       }
2169       if (annotation.groupRef != null)
2170       {
2171         String groupIdr = groupRefs.get(annotation.groupRef);
2172         if (groupIdr == null)
2173         {
2174           // make a locally unique String
2175           groupRefs.put(annotation.groupRef,
2176                   groupIdr = ("" + System.currentTimeMillis()
2177                           + annotation.groupRef.getName()
2178                           + groupRefs.size()));
2179         }
2180         an.setGroupRef(groupIdr.toString());
2181       }
2182
2183       // store all visualization attributes for annotation
2184       an.setGraphHeight(annotation.graphHeight);
2185       an.setCentreColLabels(annotation.centreColLabels);
2186       an.setScaleColLabels(annotation.scaleColLabel);
2187       an.setShowAllColLabels(annotation.showAllColLabels);
2188       an.setBelowAlignment(annotation.belowAlignment);
2189
2190       if (annotation.graph > 0)
2191       {
2192         an.setGraph(true);
2193         an.setGraphType(annotation.graph);
2194         an.setGraphGroup(annotation.graphGroup);
2195         if (annotation.getThreshold() != null)
2196         {
2197           ThresholdLine line = new ThresholdLine();
2198           line.setLabel(annotation.getThreshold().label);
2199           line.setValue(annotation.getThreshold().value);
2200           line.setColour(annotation.getThreshold().colour.getRGB());
2201           an.setThresholdLine(line);
2202         }
2203       }
2204       else
2205       {
2206         an.setGraph(false);
2207       }
2208
2209       an.setLabel(annotation.label);
2210
2211       if (annotation == av.getAlignmentQualityAnnot()
2212               || annotation == av.getAlignmentConservationAnnotation()
2213               || annotation == av.getAlignmentConsensusAnnotation()
2214               || annotation.autoCalculated)
2215       {
2216         // new way of indicating autocalculated annotation -
2217         an.setAutoCalculated(annotation.autoCalculated);
2218       }
2219       if (annotation.hasScore())
2220       {
2221         an.setScore(annotation.getScore());
2222       }
2223
2224       if (annotation.getCalcId() != null)
2225       {
2226         calcIdSet.add(annotation.getCalcId());
2227         an.setCalcId(annotation.getCalcId());
2228       }
2229       if (annotation.hasProperties())
2230       {
2231         for (String pr : annotation.getProperties())
2232         {
2233           jalview.xml.binding.jalview.Annotation.Property prop = new jalview.xml.binding.jalview.Annotation.Property();
2234           prop.setName(pr);
2235           prop.setValue(annotation.getProperty(pr));
2236           // an.addProperty(prop);
2237           an.getProperty().add(prop);
2238         }
2239       }
2240
2241       AnnotationElement ae;
2242       if (annotation.annotations != null)
2243       {
2244         an.setScoreOnly(false);
2245         for (int a = 0; a < annotation.annotations.length; a++)
2246         {
2247           if ((annotation == null) || (annotation.annotations[a] == null))
2248           {
2249             continue;
2250           }
2251
2252           ae = new AnnotationElement();
2253           if (annotation.annotations[a].description != null)
2254           {
2255             ae.setDescription(annotation.annotations[a].description);
2256           }
2257           if (annotation.annotations[a].displayCharacter != null)
2258           {
2259             ae.setDisplayCharacter(
2260                     annotation.annotations[a].displayCharacter);
2261           }
2262
2263           if (!Float.isNaN(annotation.annotations[a].value))
2264           {
2265             ae.setValue(annotation.annotations[a].value);
2266           }
2267
2268           ae.setPosition(a);
2269           if (annotation.annotations[a].secondaryStructure > ' ')
2270           {
2271             ae.setSecondaryStructure(
2272                     annotation.annotations[a].secondaryStructure + "");
2273           }
2274
2275           if (annotation.annotations[a].colour != null
2276                   && annotation.annotations[a].colour != java.awt.Color.black)
2277           {
2278             ae.setColour(annotation.annotations[a].colour.getRGB());
2279           }
2280
2281           // an.addAnnotationElement(ae);
2282           an.getAnnotationElement().add(ae);
2283           if (annotation.autoCalculated)
2284           {
2285             // only write one non-null entry into the annotation row -
2286             // sufficient to get the visualization attributes necessary to
2287             // display data
2288             continue;
2289           }
2290         }
2291       }
2292       else
2293       {
2294         an.setScoreOnly(true);
2295       }
2296       if (!storeDS || (storeDS && !annotation.autoCalculated))
2297       {
2298         // skip autocalculated annotation - these are only provided for
2299         // alignments
2300         // vamsasSet.addAnnotation(an);
2301         vamsasSet.getAnnotation().add(an);
2302       }
2303     }
2304
2305   }
2306
2307   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
2308   {
2309     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
2310     if (settings != null)
2311     {
2312       CalcIdParam vCalcIdParam = new CalcIdParam();
2313       vCalcIdParam.setCalcId(calcId);
2314       // vCalcIdParam.addServiceURL(settings.getServiceURI());
2315       vCalcIdParam.getServiceURL().add(settings.getServiceURI());
2316       // generic URI allowing a third party to resolve another instance of the
2317       // service used for this calculation
2318       for (String url : settings.getServiceURLs())
2319       {
2320         // vCalcIdParam.addServiceURL(urls);
2321         vCalcIdParam.getServiceURL().add(url);
2322       }
2323       vCalcIdParam.setVersion("1.0");
2324       if (settings.getPreset() != null)
2325       {
2326         WsParamSetI setting = settings.getPreset();
2327         vCalcIdParam.setName(setting.getName());
2328         vCalcIdParam.setDescription(setting.getDescription());
2329       }
2330       else
2331       {
2332         vCalcIdParam.setName("");
2333         vCalcIdParam.setDescription("Last used parameters");
2334       }
2335       // need to be able to recover 1) settings 2) user-defined presets or
2336       // recreate settings from preset 3) predefined settings provided by
2337       // service - or settings that can be transferred (or discarded)
2338       vCalcIdParam.setParameters(
2339               settings.getWsParamFile().replace("\n", "|\\n|"));
2340       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
2341       // todo - decide if updateImmediately is needed for any projects.
2342
2343       return vCalcIdParam;
2344     }
2345     return null;
2346   }
2347
2348   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
2349           AlignViewport av)
2350   {
2351     if (calcIdParam.getVersion().equals("1.0"))
2352     {
2353       final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
2354       Jws2Instance service = Jws2Discoverer.getDiscoverer()
2355               .getPreferredServiceFor(calcIds);
2356       if (service != null)
2357       {
2358         WsParamSetI parmSet = null;
2359         try
2360         {
2361           parmSet = service.getParamStore().parseServiceParameterFile(
2362                   calcIdParam.getName(), calcIdParam.getDescription(),
2363                   calcIds,
2364                   calcIdParam.getParameters().replace("|\\n|", "\n"));
2365         } catch (IOException x)
2366         {
2367           warn("Couldn't parse parameter data for "
2368                   + calcIdParam.getCalcId(), x);
2369           return false;
2370         }
2371         List<ArgumentI> argList = null;
2372         if (calcIdParam.getName().length() > 0)
2373         {
2374           parmSet = service.getParamStore()
2375                   .getPreset(calcIdParam.getName());
2376           if (parmSet != null)
2377           {
2378             // TODO : check we have a good match with settings in AACon -
2379             // otherwise we'll need to create a new preset
2380           }
2381         }
2382         else
2383         {
2384           argList = parmSet.getArguments();
2385           parmSet = null;
2386         }
2387         AAConSettings settings = new AAConSettings(
2388                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
2389         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
2390                 calcIdParam.isNeedsUpdate());
2391         return true;
2392       }
2393       else
2394       {
2395         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
2396         return false;
2397       }
2398     }
2399     throw new Error(MessageManager.formatMessage(
2400             "error.unsupported_version_calcIdparam", new Object[]
2401             { calcIdParam.toString() }));
2402   }
2403
2404   /**
2405    * External mapping between jalview objects and objects yielding a valid and
2406    * unique object ID string. This is null for normal Jalview project IO, but
2407    * non-null when a jalview project is being read or written as part of a
2408    * vamsas session.
2409    */
2410   IdentityHashMap jv2vobj = null;
2411
2412   /**
2413    * Construct a unique ID for jvobj using either existing bindings or if none
2414    * exist, the result of the hashcode call for the object.
2415    * 
2416    * @param jvobj
2417    *          jalview data object
2418    * @return unique ID for referring to jvobj
2419    */
2420   private String makeHashCode(Object jvobj, String altCode)
2421   {
2422     if (jv2vobj != null)
2423     {
2424       Object id = jv2vobj.get(jvobj);
2425       if (id != null)
2426       {
2427         return id.toString();
2428       }
2429       // check string ID mappings
2430       if (jvids2vobj != null && jvobj instanceof String)
2431       {
2432         id = jvids2vobj.get(jvobj);
2433       }
2434       if (id != null)
2435       {
2436         return id.toString();
2437       }
2438       // give up and warn that something has gone wrong
2439       warn("Cannot find ID for object in external mapping : " + jvobj);
2440     }
2441     return altCode;
2442   }
2443
2444   /**
2445    * return local jalview object mapped to ID, if it exists
2446    * 
2447    * @param idcode
2448    *          (may be null)
2449    * @return null or object bound to idcode
2450    */
2451   private Object retrieveExistingObj(String idcode)
2452   {
2453     if (idcode != null && vobj2jv != null)
2454     {
2455       return vobj2jv.get(idcode);
2456     }
2457     return null;
2458   }
2459
2460   /**
2461    * binding from ID strings from external mapping table to jalview data model
2462    * objects.
2463    */
2464   private Hashtable vobj2jv;
2465
2466   private Sequence createVamsasSequence(String id, SequenceI jds)
2467   {
2468     return createVamsasSequence(true, id, jds, null);
2469   }
2470
2471   private Sequence createVamsasSequence(boolean recurse, String id,
2472           SequenceI jds, SequenceI parentseq)
2473   {
2474     Sequence vamsasSeq = new Sequence();
2475     vamsasSeq.setId(id);
2476     vamsasSeq.setName(jds.getName());
2477     vamsasSeq.setSequence(jds.getSequenceAsString());
2478     vamsasSeq.setDescription(jds.getDescription());
2479     jalview.datamodel.DBRefEntry[] dbrefs = null;
2480     if (jds.getDatasetSequence() != null)
2481     {
2482       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
2483     }
2484     else
2485     {
2486       // seqId==dsseqid so we can tell which sequences really are
2487       // dataset sequences only
2488       vamsasSeq.setDsseqid(id);
2489       dbrefs = jds.getDBRefs();
2490       if (parentseq == null)
2491       {
2492         parentseq = jds;
2493       }
2494     }
2495     if (dbrefs != null)
2496     {
2497       for (int d = 0; d < dbrefs.length; d++)
2498       {
2499         DBRef dbref = new DBRef();
2500         dbref.setSource(dbrefs[d].getSource());
2501         dbref.setVersion(dbrefs[d].getVersion());
2502         dbref.setAccessionId(dbrefs[d].getAccessionId());
2503         if (dbrefs[d].hasMap())
2504         {
2505           Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
2506                   jds, recurse);
2507           dbref.setMapping(mp);
2508         }
2509         // vamsasSeq.addDBRef(dbref);
2510         vamsasSeq.getDBRef().add(dbref);
2511       }
2512     }
2513     return vamsasSeq;
2514   }
2515
2516   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
2517           SequenceI parentseq, SequenceI jds, boolean recurse)
2518   {
2519     Mapping mp = null;
2520     if (jmp.getMap() != null)
2521     {
2522       mp = new Mapping();
2523
2524       jalview.util.MapList mlst = jmp.getMap();
2525       List<int[]> r = mlst.getFromRanges();
2526       for (int[] range : r)
2527       {
2528         MapListFrom mfrom = new MapListFrom();
2529         mfrom.setStart(range[0]);
2530         mfrom.setEnd(range[1]);
2531         // mp.addMapListFrom(mfrom);
2532         mp.getMapListFrom().add(mfrom);
2533       }
2534       r = mlst.getToRanges();
2535       for (int[] range : r)
2536       {
2537         MapListTo mto = new MapListTo();
2538         mto.setStart(range[0]);
2539         mto.setEnd(range[1]);
2540         // mp.addMapListTo(mto);
2541         mp.getMapListTo().add(mto);
2542       }
2543       mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
2544       mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
2545       if (jmp.getTo() != null)
2546       {
2547         // MappingChoice mpc = new MappingChoice();
2548
2549         // check/create ID for the sequence referenced by getTo()
2550
2551         String jmpid = "";
2552         SequenceI ps = null;
2553         if (parentseq != jmp.getTo()
2554                 && parentseq.getDatasetSequence() != jmp.getTo())
2555         {
2556           // chaining dbref rather than a handshaking one
2557           jmpid = seqHash(ps = jmp.getTo());
2558         }
2559         else
2560         {
2561           jmpid = seqHash(ps = parentseq);
2562         }
2563         // mpc.setDseqFor(jmpid);
2564         mp.setDseqFor(jmpid);
2565         if (!seqRefIds.containsKey(jmpid))
2566         {
2567           jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2568           seqRefIds.put(jmpid, ps);
2569         }
2570         else
2571         {
2572           jalview.bin.Cache.log.debug("reusing DseqFor ID");
2573         }
2574
2575         // mp.setMappingChoice(mpc);
2576       }
2577     }
2578     return mp;
2579   }
2580
2581   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2582           List<UserColourScheme> userColours, JalviewModel jm)
2583   {
2584     String id = null;
2585     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2586     boolean newucs = false;
2587     if (!userColours.contains(ucs))
2588     {
2589       userColours.add(ucs);
2590       newucs = true;
2591     }
2592     id = "ucs" + userColours.indexOf(ucs);
2593     if (newucs)
2594     {
2595       // actually create the scheme's entry in the XML model
2596       java.awt.Color[] colours = ucs.getColours();
2597       UserColours uc = new UserColours();
2598       // UserColourScheme jbucs = new UserColourScheme();
2599       JalviewUserColours jbucs = new JalviewUserColours();
2600
2601       for (int i = 0; i < colours.length; i++)
2602       {
2603         Colour col = new Colour();
2604         col.setName(ResidueProperties.aa[i]);
2605         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2606         // jbucs.addColour(col);
2607         jbucs.getColour().add(col);
2608       }
2609       if (ucs.getLowerCaseColours() != null)
2610       {
2611         colours = ucs.getLowerCaseColours();
2612         for (int i = 0; i < colours.length; i++)
2613         {
2614           Colour col = new Colour();
2615           col.setName(ResidueProperties.aa[i].toLowerCase());
2616           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2617           // jbucs.addColour(col);
2618           jbucs.getColour().add(col);
2619         }
2620       }
2621
2622       uc.setId(id);
2623       uc.setUserColourScheme(jbucs);
2624       // jm.addUserColours(uc);
2625       jm.getUserColours().add(uc);
2626     }
2627
2628     return id;
2629   }
2630
2631   jalview.schemes.UserColourScheme getUserColourScheme(
2632           JalviewModel jm, String id)
2633   {
2634     List<UserColours> uc = jm.getUserColours();
2635     UserColours colours = null;
2636 /*
2637     for (int i = 0; i < uc.length; i++)
2638     {
2639       if (uc[i].getId().equals(id))
2640       {
2641         colours = uc[i];
2642         break;
2643       }
2644     }
2645 */
2646     for (UserColours c : uc)
2647     {
2648       if (c.getId().equals(id))
2649       {
2650         colours = c;
2651         break;
2652       }
2653     }
2654
2655     java.awt.Color[] newColours = new java.awt.Color[24];
2656
2657     for (int i = 0; i < 24; i++)
2658     {
2659       newColours[i] = new java.awt.Color(Integer.parseInt(
2660               // colours.getUserColourScheme().getColour(i).getRGB(), 16));
2661               colours.getUserColourScheme().getColour().get(i).getRGB(),
2662               16));
2663     }
2664
2665     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2666             newColours);
2667
2668     if (colours.getUserColourScheme().getColour().size()/*Count()*/ > 24)
2669     {
2670       newColours = new java.awt.Color[23];
2671       for (int i = 0; i < 23; i++)
2672       {
2673         newColours[i] = new java.awt.Color(Integer.parseInt(
2674                 colours.getUserColourScheme().getColour().get(i + 24)
2675                         .getRGB(),
2676                 16));
2677       }
2678       ucs.setLowerCaseColours(newColours);
2679     }
2680
2681     return ucs;
2682   }
2683
2684   /**
2685    * contains last error message (if any) encountered by XML loader.
2686    */
2687   String errorMessage = null;
2688
2689   /**
2690    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2691    * exceptions are raised during project XML parsing
2692    */
2693   public boolean attemptversion1parse = false;
2694
2695   /**
2696    * Load a jalview project archive from a jar file
2697    * 
2698    * @param file
2699    *          - HTTP URL or filename
2700    */
2701   public AlignFrame loadJalviewAlign(final String file)
2702   {
2703
2704     jalview.gui.AlignFrame af = null;
2705
2706     try
2707     {
2708       // create list to store references for any new Jmol viewers created
2709       newStructureViewers = new Vector<>();
2710       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2711       // Workaround is to make sure caller implements the JarInputStreamProvider
2712       // interface
2713       // so we can re-open the jar input stream for each entry.
2714
2715       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2716       af = loadJalviewAlign(jprovider);
2717       if (af != null)
2718       {
2719         af.setMenusForViewport();
2720       }
2721     } catch (MalformedURLException e)
2722     {
2723       errorMessage = "Invalid URL format for '" + file + "'";
2724       reportErrors();
2725     } finally
2726     {
2727       try
2728       {
2729         SwingUtilities.invokeAndWait(new Runnable()
2730         {
2731           @Override
2732           public void run()
2733           {
2734             setLoadingFinishedForNewStructureViewers();
2735           };
2736         });
2737       } catch (Exception x)
2738       {
2739         System.err.println("Error loading alignment: " + x.getMessage());
2740       }
2741     }
2742     return af;
2743   }
2744
2745   private jarInputStreamProvider createjarInputStreamProvider(
2746           final String file) throws MalformedURLException
2747   {
2748     URL url = null;
2749     errorMessage = null;
2750     uniqueSetSuffix = null;
2751     seqRefIds = null;
2752     viewportsAdded.clear();
2753     frefedSequence = null;
2754
2755     if (file.startsWith("http://"))
2756     {
2757       url = new URL(file);
2758     }
2759     final URL _url = url;
2760     return new jarInputStreamProvider()
2761     {
2762
2763       @Override
2764       public JarInputStream getJarInputStream() throws IOException
2765       {
2766         if (_url != null)
2767         {
2768           return new JarInputStream(_url.openStream());
2769         }
2770         else
2771         {
2772           return new JarInputStream(new FileInputStream(file));
2773         }
2774       }
2775
2776       @Override
2777       public String getFilename()
2778       {
2779         return file;
2780       }
2781     };
2782   }
2783
2784   /**
2785    * Recover jalview session from a jalview project archive. Caller may
2786    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2787    * themselves. Any null fields will be initialised with default values,
2788    * non-null fields are left alone.
2789    * 
2790    * @param jprovider
2791    * @return
2792    */
2793   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2794   {
2795     errorMessage = null;
2796     if (uniqueSetSuffix == null)
2797     {
2798       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2799     }
2800     if (seqRefIds == null)
2801     {
2802       initSeqRefs();
2803     }
2804     AlignFrame af = null, _af = null;
2805     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
2806     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
2807     final String file = jprovider.getFilename();
2808     try
2809     {
2810       JarInputStream jin = null;
2811       JarEntry jarentry = null;
2812       int entryCount = 1;
2813
2814       do
2815       {
2816         jin = jprovider.getJarInputStream();
2817         for (int i = 0; i < entryCount; i++)
2818         {
2819           jarentry = jin.getNextJarEntry();
2820         }
2821
2822         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2823         {
2824           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2825           // JalviewModel object = new JalviewModel();
2826
2827           JAXBContext jc = JAXBContext
2828                   .newInstance("jalview.xml.binding.jalview");
2829           XMLStreamReader streamReader = XMLInputFactory.newInstance()
2830                   .createXMLStreamReader(jin);
2831           javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
2832           JAXBElement<JalviewModel> jbe = um
2833                   .unmarshal(streamReader, JalviewModel.class);
2834           JalviewModel object = jbe.getValue();
2835
2836           /*
2837           Unmarshaller unmar = new Unmarshaller(object);
2838           unmar.setValidation(false);
2839           object = (JalviewModel) unmar.unmarshal(in);
2840           */
2841           if (true) // !skipViewport(object))
2842           {
2843             _af = loadFromObject(object, file, true, jprovider);
2844             if (_af != null && object.getViewport().size() > 0)
2845             // getJalviewModelSequence().getViewportCount() > 0)
2846             {
2847               if (af == null)
2848               {
2849                 // store a reference to the first view
2850                 af = _af;
2851               }
2852               if (_af.getViewport().isGatherViewsHere())
2853               {
2854                 // if this is a gathered view, keep its reference since
2855                 // after gathering views, only this frame will remain
2856                 af = _af;
2857                 gatherToThisFrame.put(_af.getViewport().getSequenceSetId(),
2858                         _af);
2859               }
2860               // Save dataset to register mappings once all resolved
2861               importedDatasets.put(
2862                       af.getViewport().getAlignment().getDataset(),
2863                       af.getViewport().getAlignment().getDataset());
2864             }
2865           }
2866           entryCount++;
2867         }
2868         else if (jarentry != null)
2869         {
2870           // Some other file here.
2871           entryCount++;
2872         }
2873       } while (jarentry != null);
2874       resolveFrefedSequences();
2875     } catch (IOException ex)
2876     {
2877       ex.printStackTrace();
2878       errorMessage = "Couldn't locate Jalview XML file : " + file;
2879       System.err.println(
2880               "Exception whilst loading jalview XML file : " + ex + "\n");
2881     } catch (Exception ex)
2882     {
2883       System.err.println("Parsing as Jalview Version 2 file failed.");
2884       ex.printStackTrace(System.err);
2885       if (attemptversion1parse)
2886       {
2887         // Is Version 1 Jar file?
2888         try
2889         {
2890           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2891         } catch (Exception ex2)
2892         {
2893           System.err.println("Exception whilst loading as jalviewXMLV1:");
2894           ex2.printStackTrace();
2895           af = null;
2896         }
2897       }
2898       if (Desktop.instance != null)
2899       {
2900         Desktop.instance.stopLoading();
2901       }
2902       if (af != null)
2903       {
2904         System.out.println("Successfully loaded archive file");
2905         return af;
2906       }
2907       ex.printStackTrace();
2908
2909       System.err.println(
2910               "Exception whilst loading jalview XML file : " + ex + "\n");
2911     } catch (OutOfMemoryError e)
2912     {
2913       // Don't use the OOM Window here
2914       errorMessage = "Out of memory loading jalview XML file";
2915       System.err.println("Out of memory whilst loading jalview XML file");
2916       e.printStackTrace();
2917     }
2918
2919     /*
2920      * Regather multiple views (with the same sequence set id) to the frame (if
2921      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2922      * views instead of separate frames. Note this doesn't restore a state where
2923      * some expanded views in turn have tabbed views - the last "first tab" read
2924      * in will play the role of gatherer for all.
2925      */
2926     for (AlignFrame fr : gatherToThisFrame.values())
2927     {
2928       Desktop.instance.gatherViews(fr);
2929     }
2930
2931     restoreSplitFrames();
2932     for (AlignmentI ds : importedDatasets.keySet())
2933     {
2934       if (ds.getCodonFrames() != null)
2935       {
2936         StructureSelectionManager
2937                 .getStructureSelectionManager(Desktop.instance)
2938                 .registerMappings(ds.getCodonFrames());
2939       }
2940     }
2941     if (errorMessage != null)
2942     {
2943       reportErrors();
2944     }
2945
2946     if (Desktop.instance != null)
2947     {
2948       Desktop.instance.stopLoading();
2949     }
2950
2951     return af;
2952   }
2953
2954   /**
2955    * Try to reconstruct and display SplitFrame windows, where each contains
2956    * complementary dna and protein alignments. Done by pairing up AlignFrame
2957    * objects (created earlier) which have complementary viewport ids associated.
2958    */
2959   protected void restoreSplitFrames()
2960   {
2961     List<SplitFrame> gatherTo = new ArrayList<>();
2962     List<AlignFrame> addedToSplitFrames = new ArrayList<>();
2963     Map<String, AlignFrame> dna = new HashMap<>();
2964
2965     /*
2966      * Identify the DNA alignments
2967      */
2968     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2969             .entrySet())
2970     {
2971       AlignFrame af = candidate.getValue();
2972       if (af.getViewport().getAlignment().isNucleotide())
2973       {
2974         dna.put(candidate.getKey().getId(), af);
2975       }
2976     }
2977
2978     /*
2979      * Try to match up the protein complements
2980      */
2981     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2982             .entrySet())
2983     {
2984       AlignFrame af = candidate.getValue();
2985       if (!af.getViewport().getAlignment().isNucleotide())
2986       {
2987         String complementId = candidate.getKey().getComplementId();
2988         // only non-null complements should be in the Map
2989         if (complementId != null && dna.containsKey(complementId))
2990         {
2991           final AlignFrame dnaFrame = dna.get(complementId);
2992           SplitFrame sf = createSplitFrame(dnaFrame, af);
2993           addedToSplitFrames.add(dnaFrame);
2994           addedToSplitFrames.add(af);
2995           dnaFrame.setMenusForViewport();
2996           af.setMenusForViewport();
2997           if (af.getViewport().isGatherViewsHere())
2998           {
2999             gatherTo.add(sf);
3000           }
3001         }
3002       }
3003     }
3004
3005     /*
3006      * Open any that we failed to pair up (which shouldn't happen!) as
3007      * standalone AlignFrame's.
3008      */
3009     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3010             .entrySet())
3011     {
3012       AlignFrame af = candidate.getValue();
3013       if (!addedToSplitFrames.contains(af))
3014       {
3015         Viewport view = candidate.getKey();
3016         Desktop.addInternalFrame(af, view.getTitle(),
3017                 safeInt(view.getWidth()), safeInt(view.getHeight()));
3018         af.setMenusForViewport();
3019         System.err.println("Failed to restore view " + view.getTitle()
3020                 + " to split frame");
3021       }
3022     }
3023
3024     /*
3025      * Gather back into tabbed views as flagged.
3026      */
3027     for (SplitFrame sf : gatherTo)
3028     {
3029       Desktop.instance.gatherViews(sf);
3030     }
3031
3032     splitFrameCandidates.clear();
3033   }
3034
3035   /**
3036    * Construct and display one SplitFrame holding DNA and protein alignments.
3037    * 
3038    * @param dnaFrame
3039    * @param proteinFrame
3040    * @return
3041    */
3042   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
3043           AlignFrame proteinFrame)
3044   {
3045     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
3046     String title = MessageManager.getString("label.linked_view_title");
3047     int width = (int) dnaFrame.getBounds().getWidth();
3048     int height = (int) (dnaFrame.getBounds().getHeight()
3049             + proteinFrame.getBounds().getHeight() + 50);
3050
3051     /*
3052      * SplitFrame location is saved to both enclosed frames
3053      */
3054     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
3055     Desktop.addInternalFrame(splitFrame, title, width, height);
3056
3057     /*
3058      * And compute cDNA consensus (couldn't do earlier with consensus as
3059      * mappings were not yet present)
3060      */
3061     proteinFrame.getViewport().alignmentChanged(proteinFrame.alignPanel);
3062
3063     return splitFrame;
3064   }
3065
3066   /**
3067    * check errorMessage for a valid error message and raise an error box in the
3068    * GUI or write the current errorMessage to stderr and then clear the error
3069    * state.
3070    */
3071   protected void reportErrors()
3072   {
3073     reportErrors(false);
3074   }
3075
3076   protected void reportErrors(final boolean saving)
3077   {
3078     if (errorMessage != null)
3079     {
3080       final String finalErrorMessage = errorMessage;
3081       if (raiseGUI)
3082       {
3083         javax.swing.SwingUtilities.invokeLater(new Runnable()
3084         {
3085           @Override
3086           public void run()
3087           {
3088             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3089                     finalErrorMessage,
3090                     "Error " + (saving ? "saving" : "loading")
3091                             + " Jalview file",
3092                     JvOptionPane.WARNING_MESSAGE);
3093           }
3094         });
3095       }
3096       else
3097       {
3098         System.err.println("Problem loading Jalview file: " + errorMessage);
3099       }
3100     }
3101     errorMessage = null;
3102   }
3103
3104   Map<String, String> alreadyLoadedPDB = new HashMap<>();
3105
3106   /**
3107    * when set, local views will be updated from view stored in JalviewXML
3108    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
3109    * sync if this is set to true.
3110    */
3111   private final boolean updateLocalViews = false;
3112
3113   /**
3114    * Returns the path to a temporary file holding the PDB file for the given PDB
3115    * id. The first time of asking, searches for a file of that name in the
3116    * Jalview project jar, and copies it to a new temporary file. Any repeat
3117    * requests just return the path to the file previously created.
3118    * 
3119    * @param jprovider
3120    * @param pdbId
3121    * @return
3122    */
3123   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
3124           String origFile)
3125   {
3126     if (alreadyLoadedPDB.containsKey(pdbId))
3127     {
3128       return alreadyLoadedPDB.get(pdbId).toString();
3129     }
3130
3131     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
3132             origFile);
3133     if (tempFile != null)
3134     {
3135       alreadyLoadedPDB.put(pdbId, tempFile);
3136     }
3137     return tempFile;
3138   }
3139
3140   /**
3141    * Copies the jar entry of given name to a new temporary file and returns the
3142    * path to the file, or null if the entry is not found.
3143    * 
3144    * @param jprovider
3145    * @param jarEntryName
3146    * @param prefix
3147    *          a prefix for the temporary file name, must be at least three
3148    *          characters long
3149    * @param origFile
3150    *          null or original file - so new file can be given the same suffix
3151    *          as the old one
3152    * @return
3153    */
3154   protected String copyJarEntry(jarInputStreamProvider jprovider,
3155           String jarEntryName, String prefix, String origFile)
3156   {
3157     BufferedReader in = null;
3158     PrintWriter out = null;
3159     String suffix = ".tmp";
3160     if (origFile == null)
3161     {
3162       origFile = jarEntryName;
3163     }
3164     int sfpos = origFile.lastIndexOf(".");
3165     if (sfpos > -1 && sfpos < (origFile.length() - 3))
3166     {
3167       suffix = "." + origFile.substring(sfpos + 1);
3168     }
3169     try
3170     {
3171       JarInputStream jin = jprovider.getJarInputStream();
3172       /*
3173        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
3174        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
3175        * FileInputStream(jprovider)); }
3176        */
3177
3178       JarEntry entry = null;
3179       do
3180       {
3181         entry = jin.getNextJarEntry();
3182       } while (entry != null && !entry.getName().equals(jarEntryName));
3183       if (entry != null)
3184       {
3185         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
3186         File outFile = File.createTempFile(prefix, suffix);
3187         outFile.deleteOnExit();
3188         out = new PrintWriter(new FileOutputStream(outFile));
3189         String data;
3190
3191         while ((data = in.readLine()) != null)
3192         {
3193           out.println(data);
3194         }
3195         out.flush();
3196         String t = outFile.getAbsolutePath();
3197         return t;
3198       }
3199       else
3200       {
3201         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
3202       }
3203     } catch (Exception ex)
3204     {
3205       ex.printStackTrace();
3206     } finally
3207     {
3208       if (in != null)
3209       {
3210         try
3211         {
3212           in.close();
3213         } catch (IOException e)
3214         {
3215           // ignore
3216         }
3217       }
3218       if (out != null)
3219       {
3220         out.close();
3221       }
3222     }
3223
3224     return null;
3225   }
3226
3227   private class JvAnnotRow
3228   {
3229     public JvAnnotRow(int i, AlignmentAnnotation jaa)
3230     {
3231       order = i;
3232       template = jaa;
3233     }
3234
3235     /**
3236      * persisted version of annotation row from which to take vis properties
3237      */
3238     public jalview.datamodel.AlignmentAnnotation template;
3239
3240     /**
3241      * original position of the annotation row in the alignment
3242      */
3243     public int order;
3244   }
3245
3246   /**
3247    * Load alignment frame from jalview XML DOM object
3248    * 
3249    * @param jalviewModel
3250    *          DOM
3251    * @param file
3252    *          filename source string
3253    * @param loadTreesAndStructures
3254    *          when false only create Viewport
3255    * @param jprovider
3256    *          data source provider
3257    * @return alignment frame created from view stored in DOM
3258    */
3259   AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
3260           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
3261   {
3262     SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
3263     List<Sequence> vamsasSeqs = vamsasSet.getSequence();
3264
3265     // JalviewModelSequence jms = object.getJalviewModelSequence();
3266
3267     // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
3268     // : null;
3269     Viewport view = (jalviewModel.getViewport().size() > 0)
3270             ? jalviewModel.getViewport().get(0)
3271             : null;
3272
3273     // ////////////////////////////////
3274     // INITIALISE ALIGNMENT SEQUENCESETID AND VIEWID
3275     //
3276     //
3277     // If we just load in the same jar file again, the sequenceSetId
3278     // will be the same, and we end up with multiple references
3279     // to the same sequenceSet. We must modify this id on load
3280     // so that each load of the file gives a unique id
3281
3282     /**
3283      * used to resolve correct alignment dataset for alignments with multiple
3284      * views
3285      */
3286     String uniqueSeqSetId = null;
3287     String viewId = null;
3288     if (view != null)
3289     {
3290       uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3291       viewId = (view.getId() == null ? null
3292               : view.getId() + uniqueSetSuffix);
3293     }
3294
3295     // ////////////////////////////////
3296     // LOAD SEQUENCES
3297
3298     List<SequenceI> hiddenSeqs = null;
3299
3300     List<SequenceI> tmpseqs = new ArrayList<>();
3301
3302     boolean multipleView = false;
3303     SequenceI referenceseqForView = null;
3304     // JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
3305     List<JSeq> jseqs = jalviewModel.getJSeq();
3306     int vi = 0; // counter in vamsasSeq array
3307     for (int i = 0; i < jseqs.size(); i++)
3308     {
3309       JSeq jseq = jseqs.get(i);
3310       String seqId = jseq.getId();
3311
3312       SequenceI tmpSeq = seqRefIds.get(seqId);
3313       if (tmpSeq != null)
3314       {
3315         if (!incompleteSeqs.containsKey(seqId))
3316         {
3317           // may not need this check, but keep it for at least 2.9,1 release
3318           if (tmpSeq.getStart() != jseq.getStart()
3319                   || tmpSeq.getEnd() != jseq.getEnd())
3320           {
3321             System.err.println(
3322                     "Warning JAL-2154 regression: updating start/end for sequence "
3323                             + tmpSeq.toString() + " to " + jseq);
3324           }
3325         }
3326         else
3327         {
3328           incompleteSeqs.remove(seqId);
3329         }
3330         if (vamsasSeqs.size() > vi
3331                 && vamsasSeqs.get(vi).getId().equals(seqId))
3332         {
3333           // most likely we are reading a dataset XML document so
3334           // update from vamsasSeq section of XML for this sequence
3335           tmpSeq.setName(vamsasSeqs.get(vi).getName());
3336           tmpSeq.setDescription(vamsasSeqs.get(vi).getDescription());
3337           tmpSeq.setSequence(vamsasSeqs.get(vi).getSequence());
3338           vi++;
3339         }
3340         else
3341         {
3342           // reading multiple views, so vamsasSeq set is a subset of JSeq
3343           multipleView = true;
3344         }
3345         tmpSeq.setStart(jseq.getStart());
3346         tmpSeq.setEnd(jseq.getEnd());
3347         tmpseqs.add(tmpSeq);
3348       }
3349       else
3350       {
3351         Sequence vamsasSeq = vamsasSeqs.get(vi);
3352         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq.getName(),
3353                 vamsasSeq.getSequence());
3354         tmpSeq.setDescription(vamsasSeq.getDescription());
3355         tmpSeq.setStart(jseq.getStart());
3356         tmpSeq.setEnd(jseq.getEnd());
3357         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
3358         seqRefIds.put(vamsasSeq.getId(), tmpSeq);
3359         tmpseqs.add(tmpSeq);
3360         vi++;
3361       }
3362
3363       if (safeBoolean(jseq.isViewreference()))
3364       {
3365         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
3366       }
3367
3368       if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
3369       {
3370         if (hiddenSeqs == null)
3371         {
3372           hiddenSeqs = new ArrayList<>();
3373         }
3374
3375         hiddenSeqs.add(tmpSeq);
3376       }
3377     }
3378
3379     // /
3380     // Create the alignment object from the sequence set
3381     // ///////////////////////////////
3382     SequenceI[] orderedSeqs = tmpseqs
3383             .toArray(new SequenceI[tmpseqs.size()]);
3384
3385     AlignmentI al = null;
3386     // so we must create or recover the dataset alignment before going further
3387     // ///////////////////////////////
3388     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
3389     {
3390       // older jalview projects do not have a dataset - so creat alignment and
3391       // dataset
3392       al = new Alignment(orderedSeqs);
3393       al.setDataset(null);
3394     }
3395     else
3396     {
3397       boolean isdsal = jalviewModel.getViewport().isEmpty();
3398       if (isdsal)
3399       {
3400         // we are importing a dataset record, so
3401         // recover reference to an alignment already materialsed as dataset
3402         al = getDatasetFor(vamsasSet.getDatasetId());
3403       }
3404       if (al == null)
3405       {
3406         // materialse the alignment
3407         al = new Alignment(orderedSeqs);
3408       }
3409       if (isdsal)
3410       {
3411         addDatasetRef(vamsasSet.getDatasetId(), al);
3412       }
3413
3414       // finally, verify all data in vamsasSet is actually present in al
3415       // passing on flag indicating if it is actually a stored dataset
3416       recoverDatasetFor(vamsasSet, al, isdsal, uniqueSeqSetId);
3417     }
3418
3419     if (referenceseqForView != null)
3420     {
3421       al.setSeqrep(referenceseqForView);
3422     }
3423     // / Add the alignment properties
3424     for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
3425     {
3426       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
3427               .get(i);
3428       al.setProperty(ssp.getKey(), ssp.getValue());
3429     }
3430
3431     // ///////////////////////////////
3432
3433     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
3434     if (!multipleView)
3435     {
3436       // load sequence features, database references and any associated PDB
3437       // structures for the alignment
3438       //
3439       // prior to 2.10, this part would only be executed the first time a
3440       // sequence was encountered, but not afterwards.
3441       // now, for 2.10 projects, this is also done if the xml doc includes
3442       // dataset sequences not actually present in any particular view.
3443       //
3444       for (int i = 0; i < vamsasSeqs.size(); i++)
3445       {
3446         JSeq jseq = jseqs.get(i);
3447         if (jseq.getFeatures().size() > 0)
3448         {
3449           List<Feature> features = jseq.getFeatures();
3450           for (int f = 0; f < features.size(); f++)
3451           {
3452             Feature feat = features.get(f);
3453             SequenceFeature sf = new SequenceFeature(feat.getType(),
3454                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
3455                     safeFloat(feat.getScore()), feat.getFeatureGroup());
3456             sf.setStatus(feat.getStatus());
3457
3458             /*
3459              * load any feature attributes - include map-valued attributes
3460              */
3461             Map<String, Map<String, String>> mapAttributes = new HashMap<>();
3462             for (int od = 0; od < feat.getOtherData().size(); od++)
3463             {
3464               OtherData keyValue = feat.getOtherData().get(od);
3465               String attributeName = keyValue.getKey();
3466               String attributeValue = keyValue.getValue();
3467               if (attributeName.startsWith("LINK"))
3468               {
3469                 sf.addLink(attributeValue);
3470               }
3471               else
3472               {
3473                 String subAttribute = keyValue.getKey2();
3474                 if (subAttribute == null)
3475                 {
3476                   // simple string-valued attribute
3477                   sf.setValue(attributeName, attributeValue);
3478                 }
3479                 else
3480                 {
3481                   // attribute 'key' has sub-attribute 'key2'
3482                   if (!mapAttributes.containsKey(attributeName))
3483                   {
3484                     mapAttributes.put(attributeName, new HashMap<>());
3485                   }
3486                   mapAttributes.get(attributeName).put(subAttribute,
3487                           attributeValue);
3488                 }
3489               }
3490             }
3491             for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
3492                     .entrySet())
3493             {
3494               sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
3495             }
3496
3497             // adds feature to datasequence's feature set (since Jalview 2.10)
3498             al.getSequenceAt(i).addSequenceFeature(sf);
3499           }
3500         }
3501         if (vamsasSeqs.get(i).getDBRef().size() > 0)
3502         {
3503           // adds dbrefs to datasequence's set (since Jalview 2.10)
3504           addDBRefs(
3505                   al.getSequenceAt(i).getDatasetSequence() == null
3506                           ? al.getSequenceAt(i)
3507                           : al.getSequenceAt(i).getDatasetSequence(),
3508                   vamsasSeqs.get(i));
3509         }
3510         if (jseq.getPdbids().size() > 0)
3511         {
3512           List<Pdbids> ids = jseq.getPdbids();
3513           for (int p = 0; p < ids.size(); p++)
3514           {
3515             Pdbids pdbid = ids.get(p);
3516             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
3517             entry.setId(pdbid.getId());
3518             if (pdbid.getType() != null)
3519             {
3520               if (PDBEntry.Type.getType(pdbid.getType()) != null)
3521               {
3522                 entry.setType(PDBEntry.Type.getType(pdbid.getType()));
3523               }
3524               else
3525               {
3526                 entry.setType(PDBEntry.Type.FILE);
3527               }
3528             }
3529             // jprovider is null when executing 'New View'
3530             if (pdbid.getFile() != null && jprovider != null)
3531             {
3532               if (!pdbloaded.containsKey(pdbid.getFile()))
3533               {
3534                 entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
3535                         pdbid.getFile()));
3536               }
3537               else
3538               {
3539                 entry.setFile(pdbloaded.get(pdbid.getId()).toString());
3540               }
3541             }
3542             /*
3543             if (pdbid.getPdbentryItem() != null)
3544             {
3545               for (PdbentryItem item : pdbid.getPdbentryItem())
3546               {
3547                 for (Property pr : item.getProperty())
3548                 {
3549                   entry.setProperty(pr.getName(), pr.getValue());
3550                 }
3551               }
3552             }
3553             */
3554             for (Property prop : pdbid.getProperty())
3555             {
3556               entry.setProperty(prop.getName(), prop.getValue());
3557             }
3558             StructureSelectionManager
3559                     .getStructureSelectionManager(Desktop.instance)
3560                     .registerPDBEntry(entry);
3561             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3562             if (al.getSequenceAt(i).getDatasetSequence() != null)
3563             {
3564               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3565             }
3566             else
3567             {
3568               al.getSequenceAt(i).addPDBId(entry);
3569             }
3570           }
3571         }
3572       }
3573     } // end !multipleview
3574
3575     // ///////////////////////////////
3576     // LOAD SEQUENCE MAPPINGS
3577
3578     if (vamsasSet.getAlcodonFrame().size() > 0)
3579     {
3580       // TODO Potentially this should only be done once for all views of an
3581       // alignment
3582       List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
3583       for (int i = 0; i < alc.size(); i++)
3584       {
3585         AlignedCodonFrame cf = new AlignedCodonFrame();
3586         if (alc.get(i).getAlcodMap().size() > 0)
3587         {
3588           List<AlcodMap> maps = alc.get(i).getAlcodMap();
3589           for (int m = 0; m < maps.size(); m++)
3590           {
3591             AlcodMap map = maps.get(m);
3592             SequenceI dnaseq = seqRefIds.get(map.getDnasq());
3593             // Load Mapping
3594             jalview.datamodel.Mapping mapping = null;
3595             // attach to dna sequence reference.
3596             if (map.getMapping() != null)
3597             {
3598               mapping = addMapping(map.getMapping());
3599               if (dnaseq != null && mapping.getTo() != null)
3600               {
3601                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3602               }
3603               else
3604               {
3605                 // defer to later
3606                 frefedSequence.add(
3607                         newAlcodMapRef(map.getDnasq(), cf, mapping));
3608               }
3609             }
3610           }
3611           al.addCodonFrame(cf);
3612         }
3613       }
3614     }
3615
3616     // ////////////////////////////////
3617     // LOAD ANNOTATIONS
3618     List<JvAnnotRow> autoAlan = new ArrayList<>();
3619
3620     /*
3621      * store any annotations which forward reference a group's ID
3622      */
3623     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
3624
3625     if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
3626     {
3627       List<Annotation> an = vamsasSet.getAnnotation();
3628
3629       for (int i = 0; i < an.size(); i++)
3630       {
3631         Annotation annotation = an.get(i);
3632
3633         /**
3634          * test if annotation is automatically calculated for this view only
3635          */
3636         boolean autoForView = false;
3637         if (annotation.getLabel().equals("Quality")
3638                 || annotation.getLabel().equals("Conservation")
3639                 || annotation.getLabel().equals("Consensus"))
3640         {
3641           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3642           autoForView = true;
3643           // JAXB has no has() test; schema defaults value to false
3644           // if (!annotation.hasAutoCalculated())
3645           // {
3646           // annotation.setAutoCalculated(true);
3647           // }
3648         }
3649         if (autoForView || annotation.isAutoCalculated())
3650         {
3651           // remove ID - we don't recover annotation from other views for
3652           // view-specific annotation
3653           annotation.setId(null);
3654         }
3655
3656         // set visibility for other annotation in this view
3657         String annotationId = annotation.getId();
3658         if (annotationId != null && annotationIds.containsKey(annotationId))
3659         {
3660           AlignmentAnnotation jda = annotationIds.get(annotationId);
3661           // in principle Visible should always be true for annotation displayed
3662           // in multiple views
3663           if (annotation.isVisible() != null)
3664           {
3665             jda.visible = annotation.isVisible();
3666           }
3667
3668           al.addAnnotation(jda);
3669
3670           continue;
3671         }
3672         // Construct new annotation from model.
3673         List<AnnotationElement> ae = annotation.getAnnotationElement();
3674         jalview.datamodel.Annotation[] anot = null;
3675         java.awt.Color firstColour = null;
3676         int anpos;
3677         if (!annotation.isScoreOnly())
3678         {
3679           anot = new jalview.datamodel.Annotation[al.getWidth()];
3680           for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
3681           {
3682             AnnotationElement annElement = ae.get(aa);
3683             anpos = annElement.getPosition();
3684
3685             if (anpos >= anot.length)
3686             {
3687               continue;
3688             }
3689
3690             float value = safeFloat(annElement.getValue());
3691             anot[anpos] = new jalview.datamodel.Annotation(
3692                     annElement.getDisplayCharacter(),
3693                     annElement.getDescription(),
3694                     (annElement.getSecondaryStructure() == null
3695                             || annElement.getSecondaryStructure()
3696                                     .length() == 0)
3697                                             ? ' '
3698                                             : annElement
3699                                                     .getSecondaryStructure()
3700                                                     .charAt(0),
3701                     value);
3702             anot[anpos].colour = new Color(safeInt(annElement.getColour()));
3703             if (firstColour == null)
3704             {
3705               firstColour = anot[anpos].colour;
3706             }
3707           }
3708         }
3709         jalview.datamodel.AlignmentAnnotation jaa = null;
3710
3711         if (annotation.isGraph())
3712         {
3713           float llim = 0, hlim = 0;
3714           // if (autoForView || an[i].isAutoCalculated()) {
3715           // hlim=11f;
3716           // }
3717           jaa = new jalview.datamodel.AlignmentAnnotation(
3718                   annotation.getLabel(), annotation.getDescription(), anot,
3719                   llim, hlim, safeInt(annotation.getGraphType()));
3720
3721           jaa.graphGroup = safeInt(annotation.getGraphGroup());
3722           jaa._linecolour = firstColour;
3723           if (annotation.getThresholdLine() != null)
3724           {
3725             jaa.setThreshold(new jalview.datamodel.GraphLine(
3726                     safeFloat(annotation.getThresholdLine().getValue()),
3727                     annotation.getThresholdLine().getLabel(),
3728                     new java.awt.Color(safeInt(
3729                             annotation.getThresholdLine().getColour()))));
3730           }
3731           if (autoForView || annotation.isAutoCalculated())
3732           {
3733             // Hardwire the symbol display line to ensure that labels for
3734             // histograms are displayed
3735             jaa.hasText = true;
3736           }
3737         }
3738         else
3739         {
3740           jaa = new jalview.datamodel.AlignmentAnnotation(
3741                   annotation.getLabel(), annotation.getDescription(), anot);
3742           jaa._linecolour = firstColour;
3743         }
3744         // register new annotation
3745         if (annotation.getId() != null)
3746         {
3747           annotationIds.put(annotation.getId(), jaa);
3748           jaa.annotationId = annotation.getId();
3749         }
3750         // recover sequence association
3751         String sequenceRef = annotation.getSequenceRef();
3752         if (sequenceRef != null)
3753         {
3754           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3755           SequenceI sequence = seqRefIds.get(sequenceRef);
3756           if (sequence == null)
3757           {
3758             // in pre-2.9 projects sequence ref is to sequence name
3759             sequence = al.findName(sequenceRef);
3760           }
3761           if (sequence != null)
3762           {
3763             jaa.createSequenceMapping(sequence, 1, true);
3764             sequence.addAlignmentAnnotation(jaa);
3765           }
3766         }
3767         // and make a note of any group association
3768         if (annotation.getGroupRef() != null
3769                 && annotation.getGroupRef().length() > 0)
3770         {
3771           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3772                   .get(annotation.getGroupRef());
3773           if (aal == null)
3774           {
3775             aal = new ArrayList<>();
3776             groupAnnotRefs.put(annotation.getGroupRef(), aal);
3777           }
3778           aal.add(jaa);
3779         }
3780
3781         if (annotation.getScore() != null)
3782         {
3783           jaa.setScore(annotation.getScore().doubleValue());
3784         }
3785         if (annotation.isVisible() != null)
3786         {
3787           jaa.visible = annotation.isVisible().booleanValue();
3788         }
3789
3790         if (annotation.isCentreColLabels() != null)
3791         {
3792           jaa.centreColLabels = annotation.isCentreColLabels()
3793                   .booleanValue();
3794         }
3795
3796         if (annotation.isScaleColLabels() != null)
3797         {
3798           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
3799         }
3800         if (annotation.isAutoCalculated())
3801         {
3802           // newer files have an 'autoCalculated' flag and store calculation
3803           // state in viewport properties
3804           jaa.autoCalculated = true; // means annotation will be marked for
3805           // update at end of load.
3806         }
3807         if (annotation.getGraphHeight() != null)
3808         {
3809           jaa.graphHeight = annotation.getGraphHeight().intValue();
3810         }
3811         jaa.belowAlignment = annotation.isBelowAlignment();
3812         jaa.setCalcId(annotation.getCalcId());
3813         if (annotation.getProperty().size() > 0)
3814         {
3815           for (Annotation.Property prop : annotation
3816                   .getProperty())
3817           {
3818             jaa.setProperty(prop.getName(), prop.getValue());
3819           }
3820         }
3821         if (jaa.autoCalculated)
3822         {
3823           autoAlan.add(new JvAnnotRow(i, jaa));
3824         }
3825         else
3826         // if (!autoForView)
3827         {
3828           // add autocalculated group annotation and any user created annotation
3829           // for the view
3830           al.addAnnotation(jaa);
3831         }
3832       }
3833     }
3834     // ///////////////////////
3835     // LOAD GROUPS
3836     // Create alignment markup and styles for this view
3837     if (jalviewModel.getJGroup().size() > 0)
3838     {
3839       List<JGroup> groups = jalviewModel.getJGroup();
3840       boolean addAnnotSchemeGroup = false;
3841       for (int i = 0; i < groups.size(); i++)
3842       {
3843         JGroup jGroup = groups.get(i);
3844         ColourSchemeI cs = null;
3845         if (jGroup.getColour() != null)
3846         {
3847           if (jGroup.getColour().startsWith("ucs"))
3848           {
3849             cs = getUserColourScheme(jalviewModel, jGroup.getColour());
3850           }
3851           else if (jGroup.getColour().equals("AnnotationColourGradient")
3852                   && jGroup.getAnnotationColours() != null)
3853           {
3854             addAnnotSchemeGroup = true;
3855           }
3856           else
3857           {
3858             cs = ColourSchemeProperty.getColourScheme(al,
3859                     jGroup.getColour());
3860           }
3861         }
3862         int pidThreshold = safeInt(jGroup.getPidThreshold());
3863
3864         Vector<SequenceI> seqs = new Vector<>();
3865
3866         for (int s = 0; s < jGroup.getSeq().size(); s++)
3867         {
3868           String seqId = jGroup.getSeq().get(s);
3869           SequenceI ts = seqRefIds.get(seqId);
3870
3871           if (ts != null)
3872           {
3873             seqs.addElement(ts);
3874           }
3875         }
3876
3877         if (seqs.size() < 1)
3878         {
3879           continue;
3880         }
3881
3882         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3883                 safeBoolean(jGroup.isDisplayBoxes()),
3884                 safeBoolean(jGroup.isDisplayText()),
3885                 safeBoolean(jGroup.isColourText()),
3886                 safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
3887         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
3888         sg.getGroupColourScheme()
3889                 .setConservationInc(safeInt(jGroup.getConsThreshold()));
3890         sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
3891
3892         sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
3893         sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
3894         sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
3895         sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
3896         // attributes with a default in the schema are never null
3897           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3898           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3899           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3900         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
3901         if (jGroup.getConsThreshold() != null
3902                 && jGroup.getConsThreshold().intValue() != 0)
3903         {
3904           Conservation c = new Conservation("All", sg.getSequences(null), 0,
3905                   sg.getWidth() - 1);
3906           c.calculate();
3907           c.verdict(false, 25);
3908           sg.cs.setConservation(c);
3909         }
3910
3911         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3912         {
3913           // re-instate unique group/annotation row reference
3914           List<AlignmentAnnotation> jaal = groupAnnotRefs
3915                   .get(jGroup.getId());
3916           if (jaal != null)
3917           {
3918             for (AlignmentAnnotation jaa : jaal)
3919             {
3920               jaa.groupRef = sg;
3921               if (jaa.autoCalculated)
3922               {
3923                 // match up and try to set group autocalc alignment row for this
3924                 // annotation
3925                 if (jaa.label.startsWith("Consensus for "))
3926                 {
3927                   sg.setConsensus(jaa);
3928                 }
3929                 // match up and try to set group autocalc alignment row for this
3930                 // annotation
3931                 if (jaa.label.startsWith("Conservation for "))
3932                 {
3933                   sg.setConservationRow(jaa);
3934                 }
3935               }
3936             }
3937           }
3938         }
3939         al.addGroup(sg);
3940         if (addAnnotSchemeGroup)
3941         {
3942           // reconstruct the annotation colourscheme
3943           sg.setColourScheme(constructAnnotationColour(
3944                   jGroup.getAnnotationColours(), null, al, jalviewModel, false));
3945         }
3946       }
3947     }
3948     if (view == null)
3949     {
3950       // only dataset in this model, so just return.
3951       return null;
3952     }
3953     // ///////////////////////////////
3954     // LOAD VIEWPORT
3955
3956     AlignFrame af = null;
3957     AlignViewport av = null;
3958     // now check to see if we really need to create a new viewport.
3959     if (multipleView && viewportsAdded.size() == 0)
3960     {
3961       // We recovered an alignment for which a viewport already exists.
3962       // TODO: fix up any settings necessary for overlaying stored state onto
3963       // state recovered from another document. (may not be necessary).
3964       // we may need a binding from a viewport in memory to one recovered from
3965       // XML.
3966       // and then recover its containing af to allow the settings to be applied.
3967       // TODO: fix for vamsas demo
3968       System.err.println(
3969               "About to recover a viewport for existing alignment: Sequence set ID is "
3970                       + uniqueSeqSetId);
3971       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3972       if (seqsetobj != null)
3973       {
3974         if (seqsetobj instanceof String)
3975         {
3976           uniqueSeqSetId = (String) seqsetobj;
3977           System.err.println(
3978                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3979                           + uniqueSeqSetId);
3980         }
3981         else
3982         {
3983           System.err.println(
3984                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
3985         }
3986
3987       }
3988     }
3989     /**
3990      * indicate that annotation colours are applied across all groups (pre
3991      * Jalview 2.8.1 behaviour)
3992      */
3993     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
3994             jalviewModel.getVersion());
3995
3996     AlignmentPanel ap = null;
3997     boolean isnewview = true;
3998     if (viewId != null)
3999     {
4000       // Check to see if this alignment already has a view id == viewId
4001       jalview.gui.AlignmentPanel views[] = Desktop
4002               .getAlignmentPanels(uniqueSeqSetId);
4003       if (views != null && views.length > 0)
4004       {
4005         for (int v = 0; v < views.length; v++)
4006         {
4007           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
4008           {
4009             // recover the existing alignpanel, alignframe, viewport
4010             af = views[v].alignFrame;
4011             av = views[v].av;
4012             ap = views[v];
4013             // TODO: could even skip resetting view settings if we don't want to
4014             // change the local settings from other jalview processes
4015             isnewview = false;
4016           }
4017         }
4018       }
4019     }
4020
4021     if (isnewview)
4022     {
4023       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
4024               uniqueSeqSetId, viewId, autoAlan);
4025       av = af.getViewport();
4026       ap = af.alignPanel;
4027     }
4028
4029     /*
4030      * Load any trees, PDB structures and viewers
4031      * 
4032      * Not done if flag is false (when this method is used for New View)
4033      */
4034     if (loadTreesAndStructures)
4035     {
4036       loadTrees(jalviewModel, view, af, av, ap);
4037       loadPCAViewers(jalviewModel, ap);
4038       loadPDBStructures(jprovider, jseqs, af, ap);
4039       loadRnaViewers(jprovider, jseqs, ap);
4040     }
4041     // and finally return.
4042     return af;
4043   }
4044
4045   /**
4046    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
4047    * panel is restored from separate jar entries, two (gapped and trimmed) per
4048    * sequence and secondary structure.
4049    * 
4050    * Currently each viewer shows just one sequence and structure (gapped and
4051    * trimmed), however this method is designed to support multiple sequences or
4052    * structures in viewers if wanted in future.
4053    * 
4054    * @param jprovider
4055    * @param jseqs
4056    * @param ap
4057    */
4058   private void loadRnaViewers(jarInputStreamProvider jprovider,
4059           List<JSeq> jseqs, AlignmentPanel ap)
4060   {
4061     /*
4062      * scan the sequences for references to viewers; create each one the first
4063      * time it is referenced, add Rna models to existing viewers
4064      */
4065     for (JSeq jseq : jseqs)
4066     {
4067       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
4068       {
4069         RnaViewer viewer = jseq.getRnaViewer().get(i);
4070         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
4071                 ap);
4072
4073         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
4074         {
4075           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
4076           SequenceI seq = seqRefIds.get(jseq.getId());
4077           AlignmentAnnotation ann = this.annotationIds
4078                   .get(ss.getAnnotationId());
4079
4080           /*
4081            * add the structure to the Varna display (with session state copied
4082            * from the jar to a temporary file)
4083            */
4084           boolean gapped = safeBoolean(ss.isGapped());
4085           String rnaTitle = ss.getTitle();
4086           String sessionState = ss.getViewerState();
4087           String tempStateFile = copyJarEntry(jprovider, sessionState,
4088                   "varna", null);
4089           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
4090           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
4091         }
4092         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
4093       }
4094     }
4095   }
4096
4097   /**
4098    * Locate and return an already instantiated matching AppVarna, or create one
4099    * if not found
4100    * 
4101    * @param viewer
4102    * @param viewIdSuffix
4103    * @param ap
4104    * @return
4105    */
4106   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
4107           String viewIdSuffix, AlignmentPanel ap)
4108   {
4109     /*
4110      * on each load a suffix is appended to the saved viewId, to avoid conflicts
4111      * if load is repeated
4112      */
4113     String postLoadId = viewer.getViewId() + viewIdSuffix;
4114     for (JInternalFrame frame : getAllFrames())
4115     {
4116       if (frame instanceof AppVarna)
4117       {
4118         AppVarna varna = (AppVarna) frame;
4119         if (postLoadId.equals(varna.getViewId()))
4120         {
4121           // this viewer is already instantiated
4122           // could in future here add ap as another 'parent' of the
4123           // AppVarna window; currently just 1-to-many
4124           return varna;
4125         }
4126       }
4127     }
4128
4129     /*
4130      * viewer not found - make it
4131      */
4132     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
4133             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
4134             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
4135             safeInt(viewer.getDividerLocation()));
4136     AppVarna varna = new AppVarna(model, ap);
4137
4138     return varna;
4139   }
4140
4141   /**
4142    * Load any saved trees
4143    * 
4144    * @param jm
4145    * @param view
4146    * @param af
4147    * @param av
4148    * @param ap
4149    */
4150   protected void loadTrees(JalviewModel jm, Viewport view,
4151           AlignFrame af, AlignViewport av, AlignmentPanel ap)
4152   {
4153     // TODO result of automated refactoring - are all these parameters needed?
4154     try
4155     {
4156       for (int t = 0; t < jm.getTree().size(); t++)
4157       {
4158
4159         Tree tree = jm.getTree().get(t);
4160
4161         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
4162         if (tp == null)
4163         {
4164           tp = af.showNewickTree(new NewickFile(tree.getNewick()),
4165                   tree.getTitle(), safeInt(tree.getWidth()),
4166                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
4167                   safeInt(tree.getYpos()));
4168           if (tree.getId() != null)
4169           {
4170             // perhaps bind the tree id to something ?
4171           }
4172         }
4173         else
4174         {
4175           // update local tree attributes ?
4176           // TODO: should check if tp has been manipulated by user - if so its
4177           // settings shouldn't be modified
4178           tp.setTitle(tree.getTitle());
4179           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
4180                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
4181                   safeInt(tree.getHeight())));
4182           tp.setViewport(av); // af.viewport;
4183           // TODO: verify 'associate with all views' works still
4184           tp.getTreeCanvas().setViewport(av); // af.viewport;
4185           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
4186           // FIXME: should we use safeBoolean here ?
4187           tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
4188
4189         }
4190         if (tp == null)
4191         {
4192           warn("There was a problem recovering stored Newick tree: \n"
4193                   + tree.getNewick());
4194           continue;
4195         }
4196
4197         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
4198         tp.fitToWindow_actionPerformed(null);
4199
4200         if (tree.getFontName() != null)
4201         {
4202           tp.setTreeFont(
4203                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
4204                           safeInt(tree.getFontSize())));
4205         }
4206         else
4207         {
4208           tp.setTreeFont(
4209                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
4210                           safeInt(view.getFontSize())));
4211         }
4212
4213         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
4214         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
4215         tp.showDistances(safeBoolean(tree.isShowDistances()));
4216
4217         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
4218
4219         if (safeBoolean(tree.isCurrentTree()))
4220         {
4221           af.getViewport().setCurrentTree(tp.getTree());
4222         }
4223       }
4224
4225     } catch (Exception ex)
4226     {
4227       ex.printStackTrace();
4228     }
4229   }
4230
4231   /**
4232    * Load and link any saved structure viewers.
4233    * 
4234    * @param jprovider
4235    * @param jseqs
4236    * @param af
4237    * @param ap
4238    */
4239   protected void loadPDBStructures(jarInputStreamProvider jprovider,
4240           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
4241   {
4242     /*
4243      * Run through all PDB ids on the alignment, and collect mappings between
4244      * distinct view ids and all sequences referring to that view.
4245      */
4246     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
4247
4248     for (int i = 0; i < jseqs.size(); i++)
4249     {
4250       JSeq jseq = jseqs.get(i);
4251       if (jseq.getPdbids().size() > 0)
4252       {
4253         List<Pdbids> ids = jseq.getPdbids();
4254         for (int p = 0; p < ids.size(); p++)
4255         {
4256           Pdbids pdbid = ids.get(p);
4257           final int structureStateCount = pdbid.getStructureState().size();
4258           for (int s = 0; s < structureStateCount; s++)
4259           {
4260             // check to see if we haven't already created this structure view
4261             final StructureState structureState = pdbid
4262                     .getStructureState().get(s);
4263             String sviewid = (structureState.getViewId() == null) ? null
4264                     : structureState.getViewId() + uniqueSetSuffix;
4265             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4266             // Originally : pdbid.getFile()
4267             // : TODO: verify external PDB file recovery still works in normal
4268             // jalview project load
4269             jpdb.setFile(
4270                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4271             jpdb.setId(pdbid.getId());
4272
4273             int x = safeInt(structureState.getXpos());
4274             int y = safeInt(structureState.getYpos());
4275             int width = safeInt(structureState.getWidth());
4276             int height = safeInt(structureState.getHeight());
4277
4278             // Probably don't need to do this anymore...
4279             // Desktop.desktop.getComponentAt(x, y);
4280             // TODO: NOW: check that this recovers the PDB file correctly.
4281             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4282                     pdbid.getFile());
4283             jalview.datamodel.SequenceI seq = seqRefIds
4284                     .get(jseq.getId() + "");
4285             if (sviewid == null)
4286             {
4287               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4288                       + height;
4289             }
4290             if (!structureViewers.containsKey(sviewid))
4291             {
4292               structureViewers.put(sviewid,
4293                       new StructureViewerModel(x, y, width, height, false,
4294                               false, true, structureState.getViewId(),
4295                               structureState.getType()));
4296               // Legacy pre-2.7 conversion JAL-823 :
4297               // do not assume any view has to be linked for colour by
4298               // sequence
4299             }
4300
4301             // assemble String[] { pdb files }, String[] { id for each
4302             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4303             // seqs_file 2}, boolean[] {
4304             // linkAlignPanel,superposeWithAlignpanel}} from hash
4305             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4306             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4307                     || structureState.isAlignwithAlignPanel());
4308
4309             /*
4310              * Default colour by linked panel to false if not specified (e.g.
4311              * for pre-2.7 projects)
4312              */
4313             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4314             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4315             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4316
4317             /*
4318              * Default colour by viewer to true if not specified (e.g. for
4319              * pre-2.7 projects)
4320              */
4321             boolean colourByViewer = jmoldat.isColourByViewer();
4322             colourByViewer &= structureState.isColourByJmol();
4323             jmoldat.setColourByViewer(colourByViewer);
4324
4325             if (jmoldat.getStateData().length() < structureState
4326                     .getValue()/*Content()*/.length())
4327             {
4328               jmoldat.setStateData(structureState.getValue());// Content());
4329             }
4330             if (pdbid.getFile() != null)
4331             {
4332               File mapkey = new File(pdbid.getFile());
4333               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4334               if (seqstrmaps == null)
4335               {
4336                 jmoldat.getFileData().put(mapkey,
4337                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4338                                 pdbid.getId()));
4339               }
4340               if (!seqstrmaps.getSeqList().contains(seq))
4341               {
4342                 seqstrmaps.getSeqList().add(seq);
4343                 // TODO and chains?
4344               }
4345             }
4346             else
4347             {
4348               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");
4349               warn(errorMessage);
4350             }
4351           }
4352         }
4353       }
4354     }
4355     // Instantiate the associated structure views
4356     for (Entry<String, StructureViewerModel> entry : structureViewers
4357             .entrySet())
4358     {
4359       try
4360       {
4361         createOrLinkStructureViewer(entry, af, ap, jprovider);
4362       } catch (Exception e)
4363       {
4364         System.err.println(
4365                 "Error loading structure viewer: " + e.getMessage());
4366         // failed - try the next one
4367       }
4368     }
4369   }
4370
4371   /**
4372    * 
4373    * @param viewerData
4374    * @param af
4375    * @param ap
4376    * @param jprovider
4377    */
4378   protected void createOrLinkStructureViewer(
4379           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4380           AlignmentPanel ap, jarInputStreamProvider jprovider)
4381   {
4382     final StructureViewerModel stateData = viewerData.getValue();
4383
4384     /*
4385      * Search for any viewer windows already open from other alignment views
4386      * that exactly match the stored structure state
4387      */
4388     StructureViewerBase comp = findMatchingViewer(viewerData);
4389
4390     if (comp != null)
4391     {
4392       linkStructureViewer(ap, comp, stateData);
4393       return;
4394     }
4395
4396     /*
4397      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
4398      * "viewer_"+stateData.viewId
4399      */
4400     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
4401     {
4402       createChimeraViewer(viewerData, af, jprovider);
4403     }
4404     else
4405     {
4406       /*
4407        * else Jmol (if pre-2.9, stateData contains JMOL state string)
4408        */
4409       createJmolViewer(viewerData, af, jprovider);
4410     }
4411   }
4412
4413   /**
4414    * Create a new Chimera viewer.
4415    * 
4416    * @param data
4417    * @param af
4418    * @param jprovider
4419    */
4420   protected void createChimeraViewer(
4421           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4422           jarInputStreamProvider jprovider)
4423   {
4424     StructureViewerModel data = viewerData.getValue();
4425     String chimeraSessionFile = data.getStateData();
4426
4427     /*
4428      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
4429      * 
4430      * NB this is the 'saved' viewId as in the project file XML, _not_ the
4431      * 'uniquified' sviewid used to reconstruct the viewer here
4432      */
4433     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
4434     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
4435             "chimera", null);
4436
4437     Set<Entry<File, StructureData>> fileData = data.getFileData()
4438             .entrySet();
4439     List<PDBEntry> pdbs = new ArrayList<>();
4440     List<SequenceI[]> allseqs = new ArrayList<>();
4441     for (Entry<File, StructureData> pdb : fileData)
4442     {
4443       String filePath = pdb.getValue().getFilePath();
4444       String pdbId = pdb.getValue().getPdbId();
4445       // pdbs.add(new PDBEntry(filePath, pdbId));
4446       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4447       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4448       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4449       allseqs.add(seqs);
4450     }
4451
4452     boolean colourByChimera = data.isColourByViewer();
4453     boolean colourBySequence = data.isColourWithAlignPanel();
4454
4455     // TODO use StructureViewer as a factory here, see JAL-1761
4456     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4457     final SequenceI[][] seqsArray = allseqs
4458             .toArray(new SequenceI[allseqs.size()][]);
4459     String newViewId = viewerData.getKey();
4460
4461     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4462             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4463             colourBySequence, newViewId);
4464     cvf.setSize(data.getWidth(), data.getHeight());
4465     cvf.setLocation(data.getX(), data.getY());
4466   }
4467
4468   /**
4469    * Create a new Jmol window. First parse the Jmol state to translate filenames
4470    * loaded into the view, and record the order in which files are shown in the
4471    * Jmol view, so we can add the sequence mappings in same order.
4472    * 
4473    * @param viewerData
4474    * @param af
4475    * @param jprovider
4476    */
4477   protected void createJmolViewer(
4478           final Entry<String, StructureViewerModel> viewerData,
4479           AlignFrame af, jarInputStreamProvider jprovider)
4480   {
4481     final StructureViewerModel svattrib = viewerData.getValue();
4482     String state = svattrib.getStateData();
4483
4484     /*
4485      * Pre-2.9: state element value is the Jmol state string
4486      * 
4487      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4488      * + viewId
4489      */
4490     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4491     {
4492       state = readJarEntry(jprovider,
4493               getViewerJarEntryName(svattrib.getViewId()));
4494     }
4495
4496     List<String> pdbfilenames = new ArrayList<>();
4497     List<SequenceI[]> seqmaps = new ArrayList<>();
4498     List<String> pdbids = new ArrayList<>();
4499     StringBuilder newFileLoc = new StringBuilder(64);
4500     int cp = 0, ncp, ecp;
4501     Map<File, StructureData> oldFiles = svattrib.getFileData();
4502     while ((ncp = state.indexOf("load ", cp)) > -1)
4503     {
4504       do
4505       {
4506         // look for next filename in load statement
4507         newFileLoc.append(state.substring(cp,
4508                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4509         String oldfilenam = state.substring(ncp,
4510                 ecp = state.indexOf("\"", ncp));
4511         // recover the new mapping data for this old filename
4512         // have to normalize filename - since Jmol and jalview do
4513         // filename
4514         // translation differently.
4515         StructureData filedat = oldFiles.get(new File(oldfilenam));
4516         if (filedat == null)
4517         {
4518           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4519           filedat = oldFiles.get(new File(reformatedOldFilename));
4520         }
4521         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4522         pdbfilenames.add(filedat.getFilePath());
4523         pdbids.add(filedat.getPdbId());
4524         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4525         newFileLoc.append("\"");
4526         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4527                       // look for next file statement.
4528       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4529     }
4530     if (cp > 0)
4531     {
4532       // just append rest of state
4533       newFileLoc.append(state.substring(cp));
4534     }
4535     else
4536     {
4537       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4538       newFileLoc = new StringBuilder(state);
4539       newFileLoc.append("; load append ");
4540       for (File id : oldFiles.keySet())
4541       {
4542         // add this and any other pdb files that should be present in
4543         // the viewer
4544         StructureData filedat = oldFiles.get(id);
4545         newFileLoc.append(filedat.getFilePath());
4546         pdbfilenames.add(filedat.getFilePath());
4547         pdbids.add(filedat.getPdbId());
4548         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4549         newFileLoc.append(" \"");
4550         newFileLoc.append(filedat.getFilePath());
4551         newFileLoc.append("\"");
4552
4553       }
4554       newFileLoc.append(";");
4555     }
4556
4557     if (newFileLoc.length() == 0)
4558     {
4559       return;
4560     }
4561     int histbug = newFileLoc.indexOf("history = ");
4562     if (histbug > -1)
4563     {
4564       /*
4565        * change "history = [true|false];" to "history = [1|0];"
4566        */
4567       histbug += 10;
4568       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4569       String val = (diff == -1) ? null
4570               : newFileLoc.substring(histbug, diff);
4571       if (val != null && val.length() >= 4)
4572       {
4573         if (val.contains("e")) // eh? what can it be?
4574         {
4575           if (val.trim().equals("true"))
4576           {
4577             val = "1";
4578           }
4579           else
4580           {
4581             val = "0";
4582           }
4583           newFileLoc.replace(histbug, diff, val);
4584         }
4585       }
4586     }
4587
4588     final String[] pdbf = pdbfilenames
4589             .toArray(new String[pdbfilenames.size()]);
4590     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4591     final SequenceI[][] sq = seqmaps
4592             .toArray(new SequenceI[seqmaps.size()][]);
4593     final String fileloc = newFileLoc.toString();
4594     final String sviewid = viewerData.getKey();
4595     final AlignFrame alf = af;
4596     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4597             svattrib.getWidth(), svattrib.getHeight());
4598     try
4599     {
4600       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4601       {
4602         @Override
4603         public void run()
4604         {
4605           JalviewStructureDisplayI sview = null;
4606           try
4607           {
4608             sview = new StructureViewer(
4609                     alf.alignPanel.getStructureSelectionManager())
4610                             .createView(StructureViewer.ViewerType.JMOL,
4611                                     pdbf, id, sq, alf.alignPanel, svattrib,
4612                                     fileloc, rect, sviewid);
4613             addNewStructureViewer(sview);
4614           } catch (OutOfMemoryError ex)
4615           {
4616             new OOMWarning("restoring structure view for PDB id " + id,
4617                     (OutOfMemoryError) ex.getCause());
4618             if (sview != null && sview.isVisible())
4619             {
4620               sview.closeViewer(false);
4621               sview.setVisible(false);
4622               sview.dispose();
4623             }
4624           }
4625         }
4626       });
4627     } catch (InvocationTargetException ex)
4628     {
4629       warn("Unexpected error when opening Jmol view.", ex);
4630
4631     } catch (InterruptedException e)
4632     {
4633       // e.printStackTrace();
4634     }
4635
4636   }
4637
4638   /**
4639    * Generates a name for the entry in the project jar file to hold state
4640    * information for a structure viewer
4641    * 
4642    * @param viewId
4643    * @return
4644    */
4645   protected String getViewerJarEntryName(String viewId)
4646   {
4647     return VIEWER_PREFIX + viewId;
4648   }
4649
4650   /**
4651    * Returns any open frame that matches given structure viewer data. The match
4652    * is based on the unique viewId, or (for older project versions) the frame's
4653    * geometry.
4654    * 
4655    * @param viewerData
4656    * @return
4657    */
4658   protected StructureViewerBase findMatchingViewer(
4659           Entry<String, StructureViewerModel> viewerData)
4660   {
4661     final String sviewid = viewerData.getKey();
4662     final StructureViewerModel svattrib = viewerData.getValue();
4663     StructureViewerBase comp = null;
4664     JInternalFrame[] frames = getAllFrames();
4665     for (JInternalFrame frame : frames)
4666     {
4667       if (frame instanceof StructureViewerBase)
4668       {
4669         /*
4670          * Post jalview 2.4 schema includes structure view id
4671          */
4672         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4673                 .equals(sviewid))
4674         {
4675           comp = (StructureViewerBase) frame;
4676           break; // break added in 2.9
4677         }
4678         /*
4679          * Otherwise test for matching position and size of viewer frame
4680          */
4681         else if (frame.getX() == svattrib.getX()
4682                 && frame.getY() == svattrib.getY()
4683                 && frame.getHeight() == svattrib.getHeight()
4684                 && frame.getWidth() == svattrib.getWidth())
4685         {
4686           comp = (StructureViewerBase) frame;
4687           // no break in faint hope of an exact match on viewId
4688         }
4689       }
4690     }
4691     return comp;
4692   }
4693
4694   /**
4695    * Link an AlignmentPanel to an existing structure viewer.
4696    * 
4697    * @param ap
4698    * @param viewer
4699    * @param oldFiles
4700    * @param useinViewerSuperpos
4701    * @param usetoColourbyseq
4702    * @param viewerColouring
4703    */
4704   protected void linkStructureViewer(AlignmentPanel ap,
4705           StructureViewerBase viewer, StructureViewerModel stateData)
4706   {
4707     // NOTE: if the jalview project is part of a shared session then
4708     // view synchronization should/could be done here.
4709
4710     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4711     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4712     final boolean viewerColouring = stateData.isColourByViewer();
4713     Map<File, StructureData> oldFiles = stateData.getFileData();
4714
4715     /*
4716      * Add mapping for sequences in this view to an already open viewer
4717      */
4718     final AAStructureBindingModel binding = viewer.getBinding();
4719     for (File id : oldFiles.keySet())
4720     {
4721       // add this and any other pdb files that should be present in the
4722       // viewer
4723       StructureData filedat = oldFiles.get(id);
4724       String pdbFile = filedat.getFilePath();
4725       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4726       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4727               null);
4728       binding.addSequenceForStructFile(pdbFile, seq);
4729     }
4730     // and add the AlignmentPanel's reference to the view panel
4731     viewer.addAlignmentPanel(ap);
4732     if (useinViewerSuperpos)
4733     {
4734       viewer.useAlignmentPanelForSuperposition(ap);
4735     }
4736     else
4737     {
4738       viewer.excludeAlignmentPanelForSuperposition(ap);
4739     }
4740     if (usetoColourbyseq)
4741     {
4742       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4743     }
4744     else
4745     {
4746       viewer.excludeAlignmentPanelForColourbyseq(ap);
4747     }
4748   }
4749
4750   /**
4751    * Get all frames within the Desktop.
4752    * 
4753    * @return
4754    */
4755   protected JInternalFrame[] getAllFrames()
4756   {
4757     JInternalFrame[] frames = null;
4758     // TODO is this necessary - is it safe - risk of hanging?
4759     do
4760     {
4761       try
4762       {
4763         frames = Desktop.desktop.getAllFrames();
4764       } catch (ArrayIndexOutOfBoundsException e)
4765       {
4766         // occasional No such child exceptions are thrown here...
4767         try
4768         {
4769           Thread.sleep(10);
4770         } catch (InterruptedException f)
4771         {
4772         }
4773       }
4774     } while (frames == null);
4775     return frames;
4776   }
4777
4778   /**
4779    * Answers true if 'version' is equal to or later than 'supported', where each
4780    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4781    * changes. Development and test values for 'version' are leniently treated
4782    * i.e. answer true.
4783    * 
4784    * @param supported
4785    *          - minimum version we are comparing against
4786    * @param version
4787    *          - version of data being processsed
4788    * @return
4789    */
4790   public static boolean isVersionStringLaterThan(String supported,
4791           String version)
4792   {
4793     if (supported == null || version == null
4794             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4795             || version.equalsIgnoreCase("Test")
4796             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4797     {
4798       System.err.println("Assuming project file with "
4799               + (version == null ? "null" : version)
4800               + " is compatible with Jalview version " + supported);
4801       return true;
4802     }
4803     else
4804     {
4805       return StringUtils.compareVersions(version, supported, "b") >= 0;
4806     }
4807   }
4808
4809   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4810
4811   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4812   {
4813     if (newStructureViewers != null)
4814     {
4815       sview.getBinding().setFinishedLoadingFromArchive(false);
4816       newStructureViewers.add(sview);
4817     }
4818   }
4819
4820   protected void setLoadingFinishedForNewStructureViewers()
4821   {
4822     if (newStructureViewers != null)
4823     {
4824       for (JalviewStructureDisplayI sview : newStructureViewers)
4825       {
4826         sview.getBinding().setFinishedLoadingFromArchive(true);
4827       }
4828       newStructureViewers.clear();
4829       newStructureViewers = null;
4830     }
4831   }
4832
4833   AlignFrame loadViewport(String file, List<JSeq> JSEQ,
4834           List<SequenceI> hiddenSeqs, AlignmentI al,
4835           JalviewModel jm, Viewport view, String uniqueSeqSetId,
4836           String viewId, List<JvAnnotRow> autoAlan)
4837   {
4838     AlignFrame af = null;
4839     af = new AlignFrame(al, safeInt(view.getWidth()),
4840             safeInt(view.getHeight()), uniqueSeqSetId, viewId);
4841
4842     af.setFileName(file, FileFormat.Jalview);
4843
4844     final AlignViewport viewport = af.getViewport();
4845     for (int i = 0; i < JSEQ.size(); i++)
4846     {
4847       int colour = safeInt(JSEQ.get(i).getColour());
4848       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
4849               new Color(colour));
4850     }
4851
4852     if (al.hasSeqrep())
4853     {
4854       viewport.setColourByReferenceSeq(true);
4855       viewport.setDisplayReferenceSeq(true);
4856     }
4857
4858     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
4859
4860     if (view.getSequenceSetId() != null)
4861     {
4862       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4863
4864       viewport.setSequenceSetId(uniqueSeqSetId);
4865       if (av != null)
4866       {
4867         // propagate shared settings to this new view
4868         viewport.setHistoryList(av.getHistoryList());
4869         viewport.setRedoList(av.getRedoList());
4870       }
4871       else
4872       {
4873         viewportsAdded.put(uniqueSeqSetId, viewport);
4874       }
4875       // TODO: check if this method can be called repeatedly without
4876       // side-effects if alignpanel already registered.
4877       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4878     }
4879     // apply Hidden regions to view.
4880     if (hiddenSeqs != null)
4881     {
4882       for (int s = 0; s < JSEQ.size(); s++)
4883       {
4884         SequenceGroup hidden = new SequenceGroup();
4885         boolean isRepresentative = false;
4886         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
4887         {
4888           isRepresentative = true;
4889           SequenceI sequenceToHide = al
4890                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
4891           hidden.addSequence(sequenceToHide, false);
4892           // remove from hiddenSeqs list so we don't try to hide it twice
4893           hiddenSeqs.remove(sequenceToHide);
4894         }
4895         if (isRepresentative)
4896         {
4897           SequenceI representativeSequence = al.getSequenceAt(s);
4898           hidden.addSequence(representativeSequence, false);
4899           viewport.hideRepSequences(representativeSequence, hidden);
4900         }
4901       }
4902
4903       SequenceI[] hseqs = hiddenSeqs
4904               .toArray(new SequenceI[hiddenSeqs.size()]);
4905       viewport.hideSequence(hseqs);
4906
4907     }
4908     // recover view properties and display parameters
4909
4910     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4911     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
4912     final int pidThreshold = safeInt(view.getPidThreshold());
4913     viewport.setThreshold(pidThreshold);
4914
4915     viewport.setColourText(safeBoolean(view.isShowColourText()));
4916
4917     viewport
4918             .setConservationSelected(
4919                     safeBoolean(view.isConservationSelected()));
4920     viewport.setIncrement(safeInt(view.getConsThreshold()));
4921     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
4922     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
4923     viewport.setFont(new Font(view.getFontName(),
4924             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
4925             true);
4926     ViewStyleI vs = viewport.getViewStyle();
4927     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4928     viewport.setViewStyle(vs);
4929     // TODO: allow custom charWidth/Heights to be restored by updating them
4930     // after setting font - which means set above to false
4931     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
4932     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
4933     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4934
4935     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
4936
4937     viewport.setShowText(safeBoolean(view.isShowText()));
4938
4939     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
4940     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
4941     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
4942     viewport.setShowUnconserved(view.isShowUnconserved());
4943     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
4944
4945     if (view.getViewName() != null)
4946     {
4947       viewport.setViewName(view.getViewName());
4948       af.setInitialTabVisible();
4949     }
4950     af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
4951             safeInt(view.getWidth()), safeInt(view.getHeight()));
4952     // startSeq set in af.alignPanel.updateLayout below
4953     af.alignPanel.updateLayout();
4954     ColourSchemeI cs = null;
4955     // apply colourschemes
4956     if (view.getBgColour() != null)
4957     {
4958       if (view.getBgColour().startsWith("ucs"))
4959       {
4960         cs = getUserColourScheme(jm, view.getBgColour());
4961       }
4962       else if (view.getBgColour().startsWith("Annotation"))
4963       {
4964         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
4965         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
4966
4967         // annpos
4968
4969       }
4970       else
4971       {
4972         cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour());
4973       }
4974     }
4975
4976     viewport.setGlobalColourScheme(cs);
4977     viewport.getResidueShading().setThreshold(pidThreshold,
4978             view.isIgnoreGapsinConsensus());
4979     viewport.getResidueShading()
4980             .setConsensus(viewport.getSequenceConsensusHash());
4981     viewport.setColourAppliesToAllGroups(false);
4982
4983     if (safeBoolean(view.isConservationSelected()) && cs != null)
4984     {
4985       viewport.getResidueShading()
4986               .setConservationInc(safeInt(view.getConsThreshold()));
4987     }
4988
4989     af.changeColour(cs);
4990
4991     viewport.setColourAppliesToAllGroups(true);
4992
4993     viewport
4994             .setShowSequenceFeatures(
4995                     safeBoolean(view.isShowSequenceFeatures()));
4996
4997     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
4998     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
4999     viewport.setFollowHighlight(view.isFollowHighlight());
5000     viewport.followSelection = view.isFollowSelection();
5001     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
5002     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
5003     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
5004     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
5005     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
5006     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
5007     viewport.setShowGroupConservation(view.isShowGroupConservation());
5008
5009     // recover feature settings
5010     if (jm.getFeatureSettings() != null)
5011     {
5012       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
5013               .getFeatureRenderer();
5014       FeaturesDisplayed fdi;
5015       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
5016       String[] renderOrder = new String[jm.getFeatureSettings()
5017               .getSetting().size()];
5018       Map<String, FeatureColourI> featureColours = new Hashtable<>();
5019       Map<String, Float> featureOrder = new Hashtable<>();
5020
5021       for (int fs = 0; fs < jm.getFeatureSettings()
5022               .getSetting().size(); fs++)
5023       {
5024         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
5025         String featureType = setting.getType();
5026
5027         /*
5028          * restore feature filters (if any)
5029          */
5030         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
5031                 .getMatcherSet();
5032         if (filters != null)
5033         {
5034           FeatureMatcherSetI filter = Jalview2XML
5035                   .parseFilter(featureType, filters);
5036           if (!filter.isEmpty())
5037           {
5038             fr.setFeatureFilter(featureType, filter);
5039           }
5040         }
5041
5042         /*
5043          * restore feature colour scheme
5044          */
5045         Color maxColour = new Color(setting.getColour());
5046         if (setting.getMincolour() != null)
5047         {
5048           /*
5049            * minColour is always set unless a simple colour
5050            * (including for colour by label though it doesn't use it)
5051            */
5052           Color minColour = new Color(setting.getMincolour().intValue());
5053           Color noValueColour = minColour;
5054           NoValueColour noColour = setting.getNoValueColour();
5055           if (noColour == NoValueColour.NONE)
5056           {
5057             noValueColour = null;
5058           }
5059           else if (noColour == NoValueColour.MAX)
5060           {
5061             noValueColour = maxColour;
5062           }
5063           float min = safeFloat(safeFloat(setting.getMin()));
5064           float max = setting.getMax() == null ? 1f
5065                   : setting.getMax().floatValue();
5066           FeatureColourI gc = new FeatureColour(minColour, maxColour,
5067                   noValueColour, min, max);
5068           if (setting.getAttributeName().size() > 0)
5069           {
5070             gc.setAttributeName(setting.getAttributeName().toArray(
5071                     new String[setting.getAttributeName().size()]));
5072           }
5073           if (setting.getThreshold() != null)
5074           {
5075             gc.setThreshold(setting.getThreshold().floatValue());
5076             int threshstate = safeInt(setting.getThreshstate());
5077             // -1 = None, 0 = Below, 1 = Above threshold
5078             if (threshstate == 0)
5079             {
5080               gc.setBelowThreshold(true);
5081             }
5082             else if (threshstate == 1)
5083             {
5084               gc.setAboveThreshold(true);
5085             }
5086           }
5087           gc.setAutoScaled(true); // default
5088           if (setting.isAutoScale() != null)
5089           {
5090             gc.setAutoScaled(setting.isAutoScale());
5091           }
5092           if (setting.isColourByLabel() != null)
5093           {
5094             gc.setColourByLabel(setting.isColourByLabel());
5095           }
5096           // and put in the feature colour table.
5097           featureColours.put(featureType, gc);
5098         }
5099         else
5100         {
5101           featureColours.put(featureType,
5102                   new FeatureColour(maxColour));
5103         }
5104         renderOrder[fs] = featureType;
5105         if (setting.getOrder() != null)
5106         {
5107           featureOrder.put(featureType, setting.getOrder().floatValue());
5108         }
5109         else
5110         {
5111           featureOrder.put(featureType, new Float(
5112                   fs / jm.getFeatureSettings().getSetting().size()));
5113         }
5114         if (safeBoolean(setting.isDisplay()))
5115         {
5116           fdi.setVisible(featureType);
5117         }
5118       }
5119       Map<String, Boolean> fgtable = new Hashtable<>();
5120       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
5121       {
5122         Group grp = jm.getFeatureSettings().getGroup().get(gs);
5123         fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
5124       }
5125       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5126       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
5127       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
5128       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5129               fgtable, featureColours, 1.0f, featureOrder);
5130       fr.transferSettings(frs);
5131     }
5132
5133     if (view.getHiddenColumns().size() > 0)
5134     {
5135       for (int c = 0; c < view.getHiddenColumns().size(); c++)
5136       {
5137         final HiddenColumns hc = view.getHiddenColumns().get(c);
5138         viewport.hideColumns(safeInt(hc.getStart()),
5139                 safeInt(hc.getEnd()) /* +1 */);
5140       }
5141     }
5142     if (view.getCalcIdParam() != null)
5143     {
5144       for (CalcIdParam calcIdParam : view.getCalcIdParam())
5145       {
5146         if (calcIdParam != null)
5147         {
5148           if (recoverCalcIdParam(calcIdParam, viewport))
5149           {
5150           }
5151           else
5152           {
5153             warn("Couldn't recover parameters for "
5154                     + calcIdParam.getCalcId());
5155           }
5156         }
5157       }
5158     }
5159     af.setMenusFromViewport(viewport);
5160     af.setTitle(view.getTitle());
5161     // TODO: we don't need to do this if the viewport is aready visible.
5162     /*
5163      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
5164      * has a 'cdna/protein complement' view, in which case save it in order to
5165      * populate a SplitFrame once all views have been read in.
5166      */
5167     String complementaryViewId = view.getComplementId();
5168     if (complementaryViewId == null)
5169     {
5170       Desktop.addInternalFrame(af, view.getTitle(),
5171               safeInt(view.getWidth()), safeInt(view.getHeight()));
5172       // recompute any autoannotation
5173       af.alignPanel.updateAnnotation(false, true);
5174       reorderAutoannotation(af, al, autoAlan);
5175       af.alignPanel.alignmentChanged();
5176     }
5177     else
5178     {
5179       splitFrameCandidates.put(view, af);
5180     }
5181     return af;
5182   }
5183
5184   /**
5185    * Reads saved data to restore Colour by Annotation settings
5186    * 
5187    * @param viewAnnColour
5188    * @param af
5189    * @param al
5190    * @param model
5191    * @param checkGroupAnnColour
5192    * @return
5193    */
5194   private ColourSchemeI constructAnnotationColour(
5195           AnnotationColourScheme viewAnnColour, AlignFrame af,
5196           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
5197   {
5198     boolean propagateAnnColour = false;
5199     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
5200             : al;
5201     if (checkGroupAnnColour && al.getGroups() != null
5202             && al.getGroups().size() > 0)
5203     {
5204       // pre 2.8.1 behaviour
5205       // check to see if we should transfer annotation colours
5206       propagateAnnColour = true;
5207       for (SequenceGroup sg : al.getGroups())
5208       {
5209         if (sg.getColourScheme() instanceof AnnotationColourGradient)
5210         {
5211           propagateAnnColour = false;
5212         }
5213       }
5214     }
5215
5216     /*
5217      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
5218      */
5219     String annotationId = viewAnnColour.getAnnotation();
5220     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
5221
5222     /*
5223      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
5224      */
5225     if (matchedAnnotation == null
5226             && annAlignment.getAlignmentAnnotation() != null)
5227     {
5228       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
5229       {
5230         if (annotationId
5231                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
5232         {
5233           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
5234           break;
5235         }
5236       }
5237     }
5238     if (matchedAnnotation == null)
5239     {
5240       System.err.println("Failed to match annotation colour scheme for "
5241               + annotationId);
5242       return null;
5243     }
5244     if (matchedAnnotation.getThreshold() == null)
5245     {
5246       matchedAnnotation.setThreshold(
5247               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5248                       "Threshold", Color.black));
5249     }
5250
5251     AnnotationColourGradient cs = null;
5252     if (viewAnnColour.getColourScheme().equals("None"))
5253     {
5254       cs = new AnnotationColourGradient(matchedAnnotation,
5255               new Color(safeInt(viewAnnColour.getMinColour())),
5256               new Color(safeInt(viewAnnColour.getMaxColour())),
5257               safeInt(viewAnnColour.getAboveThreshold()));
5258     }
5259     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5260     {
5261       cs = new AnnotationColourGradient(matchedAnnotation,
5262               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5263               safeInt(viewAnnColour.getAboveThreshold()));
5264     }
5265     else
5266     {
5267       cs = new AnnotationColourGradient(matchedAnnotation,
5268               ColourSchemeProperty.getColourScheme(al,
5269                       viewAnnColour.getColourScheme()),
5270               safeInt(viewAnnColour.getAboveThreshold()));
5271     }
5272
5273     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5274     boolean useOriginalColours = safeBoolean(
5275             viewAnnColour.isPredefinedColours());
5276     cs.setSeqAssociated(perSequenceOnly);
5277     cs.setPredefinedColours(useOriginalColours);
5278
5279     if (propagateAnnColour && al.getGroups() != null)
5280     {
5281       // Also use these settings for all the groups
5282       for (int g = 0; g < al.getGroups().size(); g++)
5283       {
5284         SequenceGroup sg = al.getGroups().get(g);
5285         if (sg.getGroupColourScheme() == null)
5286         {
5287           continue;
5288         }
5289
5290         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5291                 matchedAnnotation, sg.getColourScheme(),
5292                 safeInt(viewAnnColour.getAboveThreshold()));
5293         sg.setColourScheme(groupScheme);
5294         groupScheme.setSeqAssociated(perSequenceOnly);
5295         groupScheme.setPredefinedColours(useOriginalColours);
5296       }
5297     }
5298     return cs;
5299   }
5300
5301   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5302           List<JvAnnotRow> autoAlan)
5303   {
5304     // copy over visualization settings for autocalculated annotation in the
5305     // view
5306     if (al.getAlignmentAnnotation() != null)
5307     {
5308       /**
5309        * Kludge for magic autoannotation names (see JAL-811)
5310        */
5311       String[] magicNames = new String[] { "Consensus", "Quality",
5312           "Conservation" };
5313       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5314       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5315       for (String nm : magicNames)
5316       {
5317         visan.put(nm, nullAnnot);
5318       }
5319       for (JvAnnotRow auan : autoAlan)
5320       {
5321         visan.put(auan.template.label
5322                 + (auan.template.getCalcId() == null ? ""
5323                         : "\t" + auan.template.getCalcId()),
5324                 auan);
5325       }
5326       int hSize = al.getAlignmentAnnotation().length;
5327       List<JvAnnotRow> reorder = new ArrayList<>();
5328       // work through any autoCalculated annotation already on the view
5329       // removing it if it should be placed in a different location on the
5330       // annotation panel.
5331       List<String> remains = new ArrayList<>(visan.keySet());
5332       for (int h = 0; h < hSize; h++)
5333       {
5334         jalview.datamodel.AlignmentAnnotation jalan = al
5335                 .getAlignmentAnnotation()[h];
5336         if (jalan.autoCalculated)
5337         {
5338           String k;
5339           JvAnnotRow valan = visan.get(k = jalan.label);
5340           if (jalan.getCalcId() != null)
5341           {
5342             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5343           }
5344
5345           if (valan != null)
5346           {
5347             // delete the auto calculated row from the alignment
5348             al.deleteAnnotation(jalan, false);
5349             remains.remove(k);
5350             hSize--;
5351             h--;
5352             if (valan != nullAnnot)
5353             {
5354               if (jalan != valan.template)
5355               {
5356                 // newly created autoannotation row instance
5357                 // so keep a reference to the visible annotation row
5358                 // and copy over all relevant attributes
5359                 if (valan.template.graphHeight >= 0)
5360
5361                 {
5362                   jalan.graphHeight = valan.template.graphHeight;
5363                 }
5364                 jalan.visible = valan.template.visible;
5365               }
5366               reorder.add(new JvAnnotRow(valan.order, jalan));
5367             }
5368           }
5369         }
5370       }
5371       // Add any (possibly stale) autocalculated rows that were not appended to
5372       // the view during construction
5373       for (String other : remains)
5374       {
5375         JvAnnotRow othera = visan.get(other);
5376         if (othera != nullAnnot && othera.template.getCalcId() != null
5377                 && othera.template.getCalcId().length() > 0)
5378         {
5379           reorder.add(othera);
5380         }
5381       }
5382       // now put the automatic annotation in its correct place
5383       int s = 0, srt[] = new int[reorder.size()];
5384       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5385       for (JvAnnotRow jvar : reorder)
5386       {
5387         rws[s] = jvar;
5388         srt[s++] = jvar.order;
5389       }
5390       reorder.clear();
5391       jalview.util.QuickSort.sort(srt, rws);
5392       // and re-insert the annotation at its correct position
5393       for (JvAnnotRow jvar : rws)
5394       {
5395         al.addAnnotation(jvar.template, jvar.order);
5396       }
5397       af.alignPanel.adjustAnnotationHeight();
5398     }
5399   }
5400
5401   Hashtable skipList = null;
5402
5403   /**
5404    * TODO remove this method
5405    * 
5406    * @param view
5407    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5408    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5409    *         throw new Error("Implementation Error. No skipList defined for this
5410    *         Jalview2XML instance."); } return (AlignFrame)
5411    *         skipList.get(view.getSequenceSetId()); }
5412    */
5413
5414   /**
5415    * Check if the Jalview view contained in object should be skipped or not.
5416    * 
5417    * @param object
5418    * @return true if view's sequenceSetId is a key in skipList
5419    */
5420   private boolean skipViewport(JalviewModel object)
5421   {
5422     if (skipList == null)
5423     {
5424       return false;
5425     }
5426     String id = object.getViewport().get(0).getSequenceSetId();
5427     if (skipList.containsKey(id))
5428     {
5429       if (Cache.log != null && Cache.log.isDebugEnabled())
5430       {
5431         Cache.log.debug("Skipping seuqence set id " + id);
5432       }
5433       return true;
5434     }
5435     return false;
5436   }
5437
5438   public void addToSkipList(AlignFrame af)
5439   {
5440     if (skipList == null)
5441     {
5442       skipList = new Hashtable();
5443     }
5444     skipList.put(af.getViewport().getSequenceSetId(), af);
5445   }
5446
5447   public void clearSkipList()
5448   {
5449     if (skipList != null)
5450     {
5451       skipList.clear();
5452       skipList = null;
5453     }
5454   }
5455
5456   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5457           boolean ignoreUnrefed, String uniqueSeqSetId)
5458   {
5459     jalview.datamodel.AlignmentI ds = getDatasetFor(
5460             vamsasSet.getDatasetId());
5461     AlignmentI xtant_ds = ds;
5462     if (xtant_ds == null)
5463     {
5464       // good chance we are about to create a new dataset, but check if we've
5465       // seen some of the dataset sequence IDs before.
5466       // TODO: skip this check if we are working with project generated by
5467       // version 2.11 or later
5468       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5469       if (xtant_ds != null)
5470       {
5471         ds = xtant_ds;
5472         addDatasetRef(vamsasSet.getDatasetId(), ds);
5473       }
5474     }
5475     Vector dseqs = null;
5476     if (!ignoreUnrefed)
5477     {
5478       // recovering an alignment View
5479       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5480       if (seqSetDS != null)
5481       {
5482         if (ds != null && ds != seqSetDS)
5483         {
5484           warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
5485                   + " - CDS/Protein crossreference data may be lost");
5486           if (xtant_ds != null)
5487           {
5488             // This can only happen if the unique sequence set ID was bound to a
5489             // dataset that did not contain any of the sequences in the view
5490             // currently being restored.
5491             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.");
5492           }
5493         }
5494         ds = seqSetDS;
5495         addDatasetRef(vamsasSet.getDatasetId(), ds);
5496       }
5497     }
5498     if (ds == null)
5499     {
5500       // try even harder to restore dataset
5501       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5502       // create a list of new dataset sequences
5503       dseqs = new Vector();
5504     }
5505     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5506     {
5507       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5508       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5509     }
5510     // create a new dataset
5511     if (ds == null)
5512     {
5513       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5514       dseqs.copyInto(dsseqs);
5515       ds = new jalview.datamodel.Alignment(dsseqs);
5516       debug("Created new dataset " + vamsasSet.getDatasetId()
5517               + " for alignment " + System.identityHashCode(al));
5518       addDatasetRef(vamsasSet.getDatasetId(), ds);
5519     }
5520     // set the dataset for the newly imported alignment.
5521     if (al.getDataset() == null && !ignoreUnrefed)
5522     {
5523       al.setDataset(ds);
5524       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5525       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5526     }
5527     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5528   }
5529
5530   /**
5531    * XML dataset sequence ID to materialised dataset reference
5532    */
5533   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5534
5535   /**
5536    * @return the first materialised dataset reference containing a dataset
5537    *         sequence referenced in the given view
5538    * @param list
5539    *          - sequences from the view
5540    */
5541   AlignmentI checkIfHasDataset(List<Sequence> list)
5542   {
5543     for (Sequence restoredSeq : list)
5544     {
5545       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5546       if (datasetFor != null)
5547       {
5548         return datasetFor;
5549       }
5550     }
5551     return null;
5552   }
5553
5554   /**
5555    * Register ds as the containing dataset for the dataset sequences referenced
5556    * by sequences in list
5557    * 
5558    * @param list
5559    *          - sequences in a view
5560    * @param ds
5561    */
5562   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
5563   {
5564     for (Sequence restoredSeq : list)
5565     {
5566       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
5567       if (prevDS != null && prevDS != ds)
5568       {
5569         warn("Dataset sequence appears in many datasets: "
5570                 + restoredSeq.getDsseqid());
5571         // TODO: try to merge!
5572       }
5573     }
5574   }
5575   /**
5576    * 
5577    * @param vamsasSeq
5578    *          sequence definition to create/merge dataset sequence for
5579    * @param ds
5580    *          dataset alignment
5581    * @param dseqs
5582    *          vector to add new dataset sequence to
5583    * @param ignoreUnrefed
5584    *          - when true, don't create new sequences from vamsasSeq if it's id
5585    *          doesn't already have an asssociated Jalview sequence.
5586    * @param vseqpos
5587    *          - used to reorder the sequence in the alignment according to the
5588    *          vamsasSeq array ordering, to preserve ordering of dataset
5589    */
5590   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5591           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5592   {
5593     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5594     // xRef Codon Maps
5595     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5596     boolean reorder = false;
5597     SequenceI dsq = null;
5598     if (sq != null && sq.getDatasetSequence() != null)
5599     {
5600       dsq = sq.getDatasetSequence();
5601     }
5602     else
5603     {
5604       reorder = true;
5605     }
5606     if (sq == null && ignoreUnrefed)
5607     {
5608       return;
5609     }
5610     String sqid = vamsasSeq.getDsseqid();
5611     if (dsq == null)
5612     {
5613       // need to create or add a new dataset sequence reference to this sequence
5614       if (sqid != null)
5615       {
5616         dsq = seqRefIds.get(sqid);
5617       }
5618       // check again
5619       if (dsq == null)
5620       {
5621         // make a new dataset sequence
5622         dsq = sq.createDatasetSequence();
5623         if (sqid == null)
5624         {
5625           // make up a new dataset reference for this sequence
5626           sqid = seqHash(dsq);
5627         }
5628         dsq.setVamsasId(uniqueSetSuffix + sqid);
5629         seqRefIds.put(sqid, dsq);
5630         if (ds == null)
5631         {
5632           if (dseqs != null)
5633           {
5634             dseqs.addElement(dsq);
5635           }
5636         }
5637         else
5638         {
5639           ds.addSequence(dsq);
5640         }
5641       }
5642       else
5643       {
5644         if (sq != dsq)
5645         { // make this dataset sequence sq's dataset sequence
5646           sq.setDatasetSequence(dsq);
5647           // and update the current dataset alignment
5648           if (ds == null)
5649           {
5650             if (dseqs != null)
5651             {
5652               if (!dseqs.contains(dsq))
5653               {
5654                 dseqs.add(dsq);
5655               }
5656             }
5657             else
5658             {
5659               if (ds.findIndex(dsq) < 0)
5660               {
5661                 ds.addSequence(dsq);
5662               }
5663             }
5664           }
5665         }
5666       }
5667     }
5668     // TODO: refactor this as a merge dataset sequence function
5669     // now check that sq (the dataset sequence) sequence really is the union of
5670     // all references to it
5671     // boolean pre = sq.getStart() < dsq.getStart();
5672     // boolean post = sq.getEnd() > dsq.getEnd();
5673     // if (pre || post)
5674     if (sq != dsq)
5675     {
5676       // StringBuffer sb = new StringBuffer();
5677       String newres = jalview.analysis.AlignSeq.extractGaps(
5678               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5679       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5680               && newres.length() > dsq.getLength())
5681       {
5682         // Update with the longer sequence.
5683         synchronized (dsq)
5684         {
5685           /*
5686            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5687            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5688            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5689            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5690            */
5691           dsq.setSequence(newres);
5692         }
5693         // TODO: merges will never happen if we 'know' we have the real dataset
5694         // sequence - this should be detected when id==dssid
5695         System.err.println(
5696                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5697         // + (pre ? "prepended" : "") + " "
5698         // + (post ? "appended" : ""));
5699       }
5700     }
5701     else
5702     {
5703       // sequence refs are identical. We may need to update the existing dataset
5704       // alignment with this one, though.
5705       if (ds != null && dseqs == null)
5706       {
5707         int opos = ds.findIndex(dsq);
5708         SequenceI tseq = null;
5709         if (opos != -1 && vseqpos != opos)
5710         {
5711           // remove from old position
5712           ds.deleteSequence(dsq);
5713         }
5714         if (vseqpos < ds.getHeight())
5715         {
5716           if (vseqpos != opos)
5717           {
5718             // save sequence at destination position
5719             tseq = ds.getSequenceAt(vseqpos);
5720             ds.replaceSequenceAt(vseqpos, dsq);
5721             ds.addSequence(tseq);
5722           }
5723         }
5724         else
5725         {
5726           ds.addSequence(dsq);
5727         }
5728       }
5729     }
5730   }
5731
5732   /*
5733    * TODO use AlignmentI here and in related methods - needs
5734    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5735    */
5736   Hashtable<String, AlignmentI> datasetIds = null;
5737
5738   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5739
5740   private AlignmentI getDatasetFor(String datasetId)
5741   {
5742     if (datasetIds == null)
5743     {
5744       datasetIds = new Hashtable<>();
5745       return null;
5746     }
5747     if (datasetIds.containsKey(datasetId))
5748     {
5749       return datasetIds.get(datasetId);
5750     }
5751     return null;
5752   }
5753
5754   private void addDatasetRef(String datasetId, AlignmentI dataset)
5755   {
5756     if (datasetIds == null)
5757     {
5758       datasetIds = new Hashtable<>();
5759     }
5760     datasetIds.put(datasetId, dataset);
5761   }
5762
5763   /**
5764    * make a new dataset ID for this jalview dataset alignment
5765    * 
5766    * @param dataset
5767    * @return
5768    */
5769   private String getDatasetIdRef(AlignmentI dataset)
5770   {
5771     if (dataset.getDataset() != null)
5772     {
5773       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5774     }
5775     String datasetId = makeHashCode(dataset, null);
5776     if (datasetId == null)
5777     {
5778       // make a new datasetId and record it
5779       if (dataset2Ids == null)
5780       {
5781         dataset2Ids = new IdentityHashMap<>();
5782       }
5783       else
5784       {
5785         datasetId = dataset2Ids.get(dataset);
5786       }
5787       if (datasetId == null)
5788       {
5789         datasetId = "ds" + dataset2Ids.size() + 1;
5790         dataset2Ids.put(dataset, datasetId);
5791       }
5792     }
5793     return datasetId;
5794   }
5795
5796   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5797   {
5798     for (int d = 0; d < sequence.getDBRef().size(); d++)
5799     {
5800       DBRef dr = sequence.getDBRef().get(d);
5801       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5802               dr.getSource(), dr.getVersion(), dr.getAccessionId());
5803       if (dr.getMapping() != null)
5804       {
5805         entry.setMap(addMapping(dr.getMapping()));
5806       }
5807       datasetSequence.addDBRef(entry);
5808     }
5809   }
5810
5811   private jalview.datamodel.Mapping addMapping(Mapping m)
5812   {
5813     SequenceI dsto = null;
5814     // Mapping m = dr.getMapping();
5815     int fr[] = new int[m.getMapListFrom().size() * 2];
5816     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
5817     for (int _i = 0; from.hasNext(); _i += 2)
5818     {
5819       MapListFrom mf = from.next();
5820       fr[_i] = mf.getStart();
5821       fr[_i + 1] = mf.getEnd();
5822     }
5823     int fto[] = new int[m.getMapListTo().size() * 2];
5824     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
5825     for (int _i = 0; to.hasNext(); _i += 2)
5826     {
5827       MapListTo mf = to.next();
5828       fto[_i] = mf.getStart();
5829       fto[_i + 1] = mf.getEnd();
5830     }
5831     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5832             fto, m.getMapFromUnit().intValue(),
5833             m.getMapToUnit().intValue());
5834     // if (m.getMappingChoice() != null)
5835     // {
5836     // MappingChoice mc = m.getMappingChoice();
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
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 }