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