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