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