c017d117bed9d3946e79052ff82e6b7eddb07201
[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       // TODO resort annotations here to their order in the project?
4033       av = af.getViewport();
4034       ap = af.alignPanel;
4035     }
4036
4037     /*
4038      * Load any trees, PDB structures and viewers
4039      * 
4040      * Not done if flag is false (when this method is used for New View)
4041      */
4042     if (loadTreesAndStructures)
4043     {
4044       loadTrees(jalviewModel, view, af, av, ap);
4045       loadPCAViewers(jalviewModel, ap);
4046       loadPDBStructures(jprovider, jseqs, af, ap);
4047       loadRnaViewers(jprovider, jseqs, ap);
4048     }
4049     // and finally return.
4050     return af;
4051   }
4052
4053   /**
4054    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
4055    * panel is restored from separate jar entries, two (gapped and trimmed) per
4056    * sequence and secondary structure.
4057    * 
4058    * Currently each viewer shows just one sequence and structure (gapped and
4059    * trimmed), however this method is designed to support multiple sequences or
4060    * structures in viewers if wanted in future.
4061    * 
4062    * @param jprovider
4063    * @param jseqs
4064    * @param ap
4065    */
4066   private void loadRnaViewers(jarInputStreamProvider jprovider,
4067           List<JSeq> jseqs, AlignmentPanel ap)
4068   {
4069     /*
4070      * scan the sequences for references to viewers; create each one the first
4071      * time it is referenced, add Rna models to existing viewers
4072      */
4073     for (JSeq jseq : jseqs)
4074     {
4075       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
4076       {
4077         RnaViewer viewer = jseq.getRnaViewer().get(i);
4078         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
4079                 ap);
4080
4081         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
4082         {
4083           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
4084           SequenceI seq = seqRefIds.get(jseq.getId());
4085           AlignmentAnnotation ann = this.annotationIds
4086                   .get(ss.getAnnotationId());
4087
4088           /*
4089            * add the structure to the Varna display (with session state copied
4090            * from the jar to a temporary file)
4091            */
4092           boolean gapped = safeBoolean(ss.isGapped());
4093           String rnaTitle = ss.getTitle();
4094           String sessionState = ss.getViewerState();
4095           String tempStateFile = copyJarEntry(jprovider, sessionState,
4096                   "varna", null);
4097           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
4098           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
4099         }
4100         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
4101       }
4102     }
4103   }
4104
4105   /**
4106    * Locate and return an already instantiated matching AppVarna, or create one
4107    * if not found
4108    * 
4109    * @param viewer
4110    * @param viewIdSuffix
4111    * @param ap
4112    * @return
4113    */
4114   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
4115           String viewIdSuffix, AlignmentPanel ap)
4116   {
4117     /*
4118      * on each load a suffix is appended to the saved viewId, to avoid conflicts
4119      * if load is repeated
4120      */
4121     String postLoadId = viewer.getViewId() + viewIdSuffix;
4122     for (JInternalFrame frame : getAllFrames())
4123     {
4124       if (frame instanceof AppVarna)
4125       {
4126         AppVarna varna = (AppVarna) frame;
4127         if (postLoadId.equals(varna.getViewId()))
4128         {
4129           // this viewer is already instantiated
4130           // could in future here add ap as another 'parent' of the
4131           // AppVarna window; currently just 1-to-many
4132           return varna;
4133         }
4134       }
4135     }
4136
4137     /*
4138      * viewer not found - make it
4139      */
4140     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
4141             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
4142             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
4143             safeInt(viewer.getDividerLocation()));
4144     AppVarna varna = new AppVarna(model, ap);
4145
4146     return varna;
4147   }
4148
4149   /**
4150    * Load any saved trees
4151    * 
4152    * @param jm
4153    * @param view
4154    * @param af
4155    * @param av
4156    * @param ap
4157    */
4158   protected void loadTrees(JalviewModel jm, Viewport view,
4159           AlignFrame af, AlignViewport av, AlignmentPanel ap)
4160   {
4161     // TODO result of automated refactoring - are all these parameters needed?
4162     try
4163     {
4164       for (int t = 0; t < jm.getTree().size(); t++)
4165       {
4166
4167         Tree tree = jm.getTree().get(t);
4168
4169         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
4170         if (tp == null)
4171         {
4172           tp = af.showNewickTree(new NewickFile(tree.getNewick()),
4173                   tree.getTitle(), safeInt(tree.getWidth()),
4174                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
4175                   safeInt(tree.getYpos()));
4176           if (tree.getId() != null)
4177           {
4178             // perhaps bind the tree id to something ?
4179           }
4180         }
4181         else
4182         {
4183           // update local tree attributes ?
4184           // TODO: should check if tp has been manipulated by user - if so its
4185           // settings shouldn't be modified
4186           tp.setTitle(tree.getTitle());
4187           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
4188                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
4189                   safeInt(tree.getHeight())));
4190           tp.setViewport(av); // af.viewport;
4191           // TODO: verify 'associate with all views' works still
4192           tp.getTreeCanvas().setViewport(av); // af.viewport;
4193           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
4194         }
4195         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
4196         if (tp == null)
4197         {
4198           warn("There was a problem recovering stored Newick tree: \n"
4199                   + tree.getNewick());
4200           continue;
4201         }
4202
4203         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
4204         tp.fitToWindow_actionPerformed(null);
4205
4206         if (tree.getFontName() != null)
4207         {
4208           tp.setTreeFont(
4209                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
4210                           safeInt(tree.getFontSize())));
4211         }
4212         else
4213         {
4214           tp.setTreeFont(
4215                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
4216                           safeInt(view.getFontSize())));
4217         }
4218
4219         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
4220         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
4221         tp.showDistances(safeBoolean(tree.isShowDistances()));
4222
4223         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
4224
4225         if (safeBoolean(tree.isCurrentTree()))
4226         {
4227           af.getViewport().setCurrentTree(tp.getTree());
4228         }
4229       }
4230
4231     } catch (Exception ex)
4232     {
4233       ex.printStackTrace();
4234     }
4235   }
4236
4237   /**
4238    * Load and link any saved structure viewers.
4239    * 
4240    * @param jprovider
4241    * @param jseqs
4242    * @param af
4243    * @param ap
4244    */
4245   protected void loadPDBStructures(jarInputStreamProvider jprovider,
4246           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
4247   {
4248     /*
4249      * Run through all PDB ids on the alignment, and collect mappings between
4250      * distinct view ids and all sequences referring to that view.
4251      */
4252     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
4253
4254     for (int i = 0; i < jseqs.size(); i++)
4255     {
4256       JSeq jseq = jseqs.get(i);
4257       if (jseq.getPdbids().size() > 0)
4258       {
4259         List<Pdbids> ids = jseq.getPdbids();
4260         for (int p = 0; p < ids.size(); p++)
4261         {
4262           Pdbids pdbid = ids.get(p);
4263           final int structureStateCount = pdbid.getStructureState().size();
4264           for (int s = 0; s < structureStateCount; s++)
4265           {
4266             // check to see if we haven't already created this structure view
4267             final StructureState structureState = pdbid
4268                     .getStructureState().get(s);
4269             String sviewid = (structureState.getViewId() == null) ? null
4270                     : structureState.getViewId() + uniqueSetSuffix;
4271             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4272             // Originally : pdbid.getFile()
4273             // : TODO: verify external PDB file recovery still works in normal
4274             // jalview project load
4275             jpdb.setFile(
4276                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4277             jpdb.setId(pdbid.getId());
4278
4279             int x = safeInt(structureState.getXpos());
4280             int y = safeInt(structureState.getYpos());
4281             int width = safeInt(structureState.getWidth());
4282             int height = safeInt(structureState.getHeight());
4283
4284             // Probably don't need to do this anymore...
4285             // Desktop.desktop.getComponentAt(x, y);
4286             // TODO: NOW: check that this recovers the PDB file correctly.
4287             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4288                     pdbid.getFile());
4289             jalview.datamodel.SequenceI seq = seqRefIds
4290                     .get(jseq.getId() + "");
4291             if (sviewid == null)
4292             {
4293               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4294                       + height;
4295             }
4296             if (!structureViewers.containsKey(sviewid))
4297             {
4298               structureViewers.put(sviewid,
4299                       new StructureViewerModel(x, y, width, height, false,
4300                               false, true, structureState.getViewId(),
4301                               structureState.getType()));
4302               // Legacy pre-2.7 conversion JAL-823 :
4303               // do not assume any view has to be linked for colour by
4304               // sequence
4305             }
4306
4307             // assemble String[] { pdb files }, String[] { id for each
4308             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4309             // seqs_file 2}, boolean[] {
4310             // linkAlignPanel,superposeWithAlignpanel}} from hash
4311             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4312             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4313                     || structureState.isAlignwithAlignPanel());
4314
4315             /*
4316              * Default colour by linked panel to false if not specified (e.g.
4317              * for pre-2.7 projects)
4318              */
4319             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4320             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4321             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4322
4323             /*
4324              * Default colour by viewer to true if not specified (e.g. for
4325              * pre-2.7 projects)
4326              */
4327             boolean colourByViewer = jmoldat.isColourByViewer();
4328             colourByViewer &= structureState.isColourByJmol();
4329             jmoldat.setColourByViewer(colourByViewer);
4330
4331             if (jmoldat.getStateData().length() < structureState
4332                     .getValue()/*Content()*/.length())
4333             {
4334               jmoldat.setStateData(structureState.getValue());// Content());
4335             }
4336             if (pdbid.getFile() != null)
4337             {
4338               File mapkey = new File(pdbid.getFile());
4339               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4340               if (seqstrmaps == null)
4341               {
4342                 jmoldat.getFileData().put(mapkey,
4343                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4344                                 pdbid.getId()));
4345               }
4346               if (!seqstrmaps.getSeqList().contains(seq))
4347               {
4348                 seqstrmaps.getSeqList().add(seq);
4349                 // TODO and chains?
4350               }
4351             }
4352             else
4353             {
4354               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");
4355               warn(errorMessage);
4356             }
4357           }
4358         }
4359       }
4360     }
4361     // Instantiate the associated structure views
4362     for (Entry<String, StructureViewerModel> entry : structureViewers
4363             .entrySet())
4364     {
4365       try
4366       {
4367         createOrLinkStructureViewer(entry, af, ap, jprovider);
4368       } catch (Exception e)
4369       {
4370         System.err.println(
4371                 "Error loading structure viewer: " + e.getMessage());
4372         // failed - try the next one
4373       }
4374     }
4375   }
4376
4377   /**
4378    * 
4379    * @param viewerData
4380    * @param af
4381    * @param ap
4382    * @param jprovider
4383    */
4384   protected void createOrLinkStructureViewer(
4385           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4386           AlignmentPanel ap, jarInputStreamProvider jprovider)
4387   {
4388     final StructureViewerModel stateData = viewerData.getValue();
4389
4390     /*
4391      * Search for any viewer windows already open from other alignment views
4392      * that exactly match the stored structure state
4393      */
4394     StructureViewerBase comp = findMatchingViewer(viewerData);
4395
4396     if (comp != null)
4397     {
4398       linkStructureViewer(ap, comp, stateData);
4399       return;
4400     }
4401
4402     /*
4403      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
4404      * "viewer_"+stateData.viewId
4405      */
4406     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
4407     {
4408       createChimeraViewer(viewerData, af, jprovider);
4409     }
4410     else
4411     {
4412       /*
4413        * else Jmol (if pre-2.9, stateData contains JMOL state string)
4414        */
4415       createJmolViewer(viewerData, af, jprovider);
4416     }
4417   }
4418
4419   /**
4420    * Create a new Chimera viewer.
4421    * 
4422    * @param data
4423    * @param af
4424    * @param jprovider
4425    */
4426   protected void createChimeraViewer(
4427           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4428           jarInputStreamProvider jprovider)
4429   {
4430     StructureViewerModel data = viewerData.getValue();
4431     String chimeraSessionFile = data.getStateData();
4432
4433     /*
4434      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
4435      * 
4436      * NB this is the 'saved' viewId as in the project file XML, _not_ the
4437      * 'uniquified' sviewid used to reconstruct the viewer here
4438      */
4439     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
4440     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
4441             "chimera", null);
4442
4443     Set<Entry<File, StructureData>> fileData = data.getFileData()
4444             .entrySet();
4445     List<PDBEntry> pdbs = new ArrayList<>();
4446     List<SequenceI[]> allseqs = new ArrayList<>();
4447     for (Entry<File, StructureData> pdb : fileData)
4448     {
4449       String filePath = pdb.getValue().getFilePath();
4450       String pdbId = pdb.getValue().getPdbId();
4451       // pdbs.add(new PDBEntry(filePath, pdbId));
4452       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4453       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4454       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4455       allseqs.add(seqs);
4456     }
4457
4458     boolean colourByChimera = data.isColourByViewer();
4459     boolean colourBySequence = data.isColourWithAlignPanel();
4460
4461     // TODO use StructureViewer as a factory here, see JAL-1761
4462     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4463     final SequenceI[][] seqsArray = allseqs
4464             .toArray(new SequenceI[allseqs.size()][]);
4465     String newViewId = viewerData.getKey();
4466
4467     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4468             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4469             colourBySequence, newViewId);
4470     cvf.setSize(data.getWidth(), data.getHeight());
4471     cvf.setLocation(data.getX(), data.getY());
4472   }
4473
4474   /**
4475    * Create a new Jmol window. First parse the Jmol state to translate filenames
4476    * loaded into the view, and record the order in which files are shown in the
4477    * Jmol view, so we can add the sequence mappings in same order.
4478    * 
4479    * @param viewerData
4480    * @param af
4481    * @param jprovider
4482    */
4483   protected void createJmolViewer(
4484           final Entry<String, StructureViewerModel> viewerData,
4485           AlignFrame af, jarInputStreamProvider jprovider)
4486   {
4487     final StructureViewerModel svattrib = viewerData.getValue();
4488     String state = svattrib.getStateData();
4489
4490     /*
4491      * Pre-2.9: state element value is the Jmol state string
4492      * 
4493      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4494      * + viewId
4495      */
4496     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4497     {
4498       state = readJarEntry(jprovider,
4499               getViewerJarEntryName(svattrib.getViewId()));
4500     }
4501
4502     List<String> pdbfilenames = new ArrayList<>();
4503     List<SequenceI[]> seqmaps = new ArrayList<>();
4504     List<String> pdbids = new ArrayList<>();
4505     StringBuilder newFileLoc = new StringBuilder(64);
4506     int cp = 0, ncp, ecp;
4507     Map<File, StructureData> oldFiles = svattrib.getFileData();
4508     while ((ncp = state.indexOf("load ", cp)) > -1)
4509     {
4510       do
4511       {
4512         // look for next filename in load statement
4513         newFileLoc.append(state.substring(cp,
4514                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4515         String oldfilenam = state.substring(ncp,
4516                 ecp = state.indexOf("\"", ncp));
4517         // recover the new mapping data for this old filename
4518         // have to normalize filename - since Jmol and jalview do
4519         // filename
4520         // translation differently.
4521         StructureData filedat = oldFiles.get(new File(oldfilenam));
4522         if (filedat == null)
4523         {
4524           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4525           filedat = oldFiles.get(new File(reformatedOldFilename));
4526         }
4527         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4528         pdbfilenames.add(filedat.getFilePath());
4529         pdbids.add(filedat.getPdbId());
4530         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4531         newFileLoc.append("\"");
4532         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4533                       // look for next file statement.
4534       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4535     }
4536     if (cp > 0)
4537     {
4538       // just append rest of state
4539       newFileLoc.append(state.substring(cp));
4540     }
4541     else
4542     {
4543       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4544       newFileLoc = new StringBuilder(state);
4545       newFileLoc.append("; load append ");
4546       for (File id : oldFiles.keySet())
4547       {
4548         // add this and any other pdb files that should be present in
4549         // the viewer
4550         StructureData filedat = oldFiles.get(id);
4551         newFileLoc.append(filedat.getFilePath());
4552         pdbfilenames.add(filedat.getFilePath());
4553         pdbids.add(filedat.getPdbId());
4554         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4555         newFileLoc.append(" \"");
4556         newFileLoc.append(filedat.getFilePath());
4557         newFileLoc.append("\"");
4558
4559       }
4560       newFileLoc.append(";");
4561     }
4562
4563     if (newFileLoc.length() == 0)
4564     {
4565       return;
4566     }
4567     int histbug = newFileLoc.indexOf("history = ");
4568     if (histbug > -1)
4569     {
4570       /*
4571        * change "history = [true|false];" to "history = [1|0];"
4572        */
4573       histbug += 10;
4574       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4575       String val = (diff == -1) ? null
4576               : newFileLoc.substring(histbug, diff);
4577       if (val != null && val.length() >= 4)
4578       {
4579         if (val.contains("e")) // eh? what can it be?
4580         {
4581           if (val.trim().equals("true"))
4582           {
4583             val = "1";
4584           }
4585           else
4586           {
4587             val = "0";
4588           }
4589           newFileLoc.replace(histbug, diff, val);
4590         }
4591       }
4592     }
4593
4594     final String[] pdbf = pdbfilenames
4595             .toArray(new String[pdbfilenames.size()]);
4596     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4597     final SequenceI[][] sq = seqmaps
4598             .toArray(new SequenceI[seqmaps.size()][]);
4599     final String fileloc = newFileLoc.toString();
4600     final String sviewid = viewerData.getKey();
4601     final AlignFrame alf = af;
4602     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4603             svattrib.getWidth(), svattrib.getHeight());
4604     try
4605     {
4606       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4607       {
4608         @Override
4609         public void run()
4610         {
4611           JalviewStructureDisplayI sview = null;
4612           try
4613           {
4614             sview = new StructureViewer(
4615                     alf.alignPanel.getStructureSelectionManager())
4616                             .createView(StructureViewer.ViewerType.JMOL,
4617                                     pdbf, id, sq, alf.alignPanel, svattrib,
4618                                     fileloc, rect, sviewid);
4619             addNewStructureViewer(sview);
4620           } catch (OutOfMemoryError ex)
4621           {
4622             new OOMWarning("restoring structure view for PDB id " + id,
4623                     (OutOfMemoryError) ex.getCause());
4624             if (sview != null && sview.isVisible())
4625             {
4626               sview.closeViewer(false);
4627               sview.setVisible(false);
4628               sview.dispose();
4629             }
4630           }
4631         }
4632       });
4633     } catch (InvocationTargetException ex)
4634     {
4635       warn("Unexpected error when opening Jmol view.", ex);
4636
4637     } catch (InterruptedException e)
4638     {
4639       // e.printStackTrace();
4640     }
4641
4642   }
4643
4644   /**
4645    * Generates a name for the entry in the project jar file to hold state
4646    * information for a structure viewer
4647    * 
4648    * @param viewId
4649    * @return
4650    */
4651   protected String getViewerJarEntryName(String viewId)
4652   {
4653     return VIEWER_PREFIX + viewId;
4654   }
4655
4656   /**
4657    * Returns any open frame that matches given structure viewer data. The match
4658    * is based on the unique viewId, or (for older project versions) the frame's
4659    * geometry.
4660    * 
4661    * @param viewerData
4662    * @return
4663    */
4664   protected StructureViewerBase findMatchingViewer(
4665           Entry<String, StructureViewerModel> viewerData)
4666   {
4667     final String sviewid = viewerData.getKey();
4668     final StructureViewerModel svattrib = viewerData.getValue();
4669     StructureViewerBase comp = null;
4670     JInternalFrame[] frames = getAllFrames();
4671     for (JInternalFrame frame : frames)
4672     {
4673       if (frame instanceof StructureViewerBase)
4674       {
4675         /*
4676          * Post jalview 2.4 schema includes structure view id
4677          */
4678         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4679                 .equals(sviewid))
4680         {
4681           comp = (StructureViewerBase) frame;
4682           break; // break added in 2.9
4683         }
4684         /*
4685          * Otherwise test for matching position and size of viewer frame
4686          */
4687         else if (frame.getX() == svattrib.getX()
4688                 && frame.getY() == svattrib.getY()
4689                 && frame.getHeight() == svattrib.getHeight()
4690                 && frame.getWidth() == svattrib.getWidth())
4691         {
4692           comp = (StructureViewerBase) frame;
4693           // no break in faint hope of an exact match on viewId
4694         }
4695       }
4696     }
4697     return comp;
4698   }
4699
4700   /**
4701    * Link an AlignmentPanel to an existing structure viewer.
4702    * 
4703    * @param ap
4704    * @param viewer
4705    * @param oldFiles
4706    * @param useinViewerSuperpos
4707    * @param usetoColourbyseq
4708    * @param viewerColouring
4709    */
4710   protected void linkStructureViewer(AlignmentPanel ap,
4711           StructureViewerBase viewer, StructureViewerModel stateData)
4712   {
4713     // NOTE: if the jalview project is part of a shared session then
4714     // view synchronization should/could be done here.
4715
4716     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4717     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4718     final boolean viewerColouring = stateData.isColourByViewer();
4719     Map<File, StructureData> oldFiles = stateData.getFileData();
4720
4721     /*
4722      * Add mapping for sequences in this view to an already open viewer
4723      */
4724     final AAStructureBindingModel binding = viewer.getBinding();
4725     for (File id : oldFiles.keySet())
4726     {
4727       // add this and any other pdb files that should be present in the
4728       // viewer
4729       StructureData filedat = oldFiles.get(id);
4730       String pdbFile = filedat.getFilePath();
4731       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4732       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4733               null);
4734       binding.addSequenceForStructFile(pdbFile, seq);
4735     }
4736     // and add the AlignmentPanel's reference to the view panel
4737     viewer.addAlignmentPanel(ap);
4738     if (useinViewerSuperpos)
4739     {
4740       viewer.useAlignmentPanelForSuperposition(ap);
4741     }
4742     else
4743     {
4744       viewer.excludeAlignmentPanelForSuperposition(ap);
4745     }
4746     if (usetoColourbyseq)
4747     {
4748       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4749     }
4750     else
4751     {
4752       viewer.excludeAlignmentPanelForColourbyseq(ap);
4753     }
4754   }
4755
4756   /**
4757    * Get all frames within the Desktop.
4758    * 
4759    * @return
4760    */
4761   protected JInternalFrame[] getAllFrames()
4762   {
4763     JInternalFrame[] frames = null;
4764     // TODO is this necessary - is it safe - risk of hanging?
4765     do
4766     {
4767       try
4768       {
4769         frames = Desktop.desktop.getAllFrames();
4770       } catch (ArrayIndexOutOfBoundsException e)
4771       {
4772         // occasional No such child exceptions are thrown here...
4773         try
4774         {
4775           Thread.sleep(10);
4776         } catch (InterruptedException f)
4777         {
4778         }
4779       }
4780     } while (frames == null);
4781     return frames;
4782   }
4783
4784   /**
4785    * Answers true if 'version' is equal to or later than 'supported', where each
4786    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4787    * changes. Development and test values for 'version' are leniently treated
4788    * i.e. answer true.
4789    * 
4790    * @param supported
4791    *          - minimum version we are comparing against
4792    * @param version
4793    *          - version of data being processsed
4794    * @return
4795    */
4796   public static boolean isVersionStringLaterThan(String supported,
4797           String version)
4798   {
4799     if (supported == null || version == null
4800             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4801             || version.equalsIgnoreCase("Test")
4802             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4803     {
4804       System.err.println("Assuming project file with "
4805               + (version == null ? "null" : version)
4806               + " is compatible with Jalview version " + supported);
4807       return true;
4808     }
4809     else
4810     {
4811       return StringUtils.compareVersions(version, supported, "b") >= 0;
4812     }
4813   }
4814
4815   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4816
4817   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4818   {
4819     if (newStructureViewers != null)
4820     {
4821       sview.getBinding().setFinishedLoadingFromArchive(false);
4822       newStructureViewers.add(sview);
4823     }
4824   }
4825
4826   protected void setLoadingFinishedForNewStructureViewers()
4827   {
4828     if (newStructureViewers != null)
4829     {
4830       for (JalviewStructureDisplayI sview : newStructureViewers)
4831       {
4832         sview.getBinding().setFinishedLoadingFromArchive(true);
4833       }
4834       newStructureViewers.clear();
4835       newStructureViewers = null;
4836     }
4837   }
4838
4839   AlignFrame loadViewport(String file, List<JSeq> JSEQ,
4840           List<SequenceI> hiddenSeqs, AlignmentI al,
4841           JalviewModel jm, Viewport view, String uniqueSeqSetId,
4842           String viewId, List<JvAnnotRow> autoAlan)
4843   {
4844     AlignFrame af = null;
4845     af = new AlignFrame(al, safeInt(view.getWidth()),
4846             safeInt(view.getHeight()), uniqueSeqSetId, viewId);
4847
4848     af.setFileName(file, FileFormat.Jalview);
4849
4850     final AlignViewport viewport = af.getViewport();
4851     for (int i = 0; i < JSEQ.size(); i++)
4852     {
4853       int colour = safeInt(JSEQ.get(i).getColour());
4854       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
4855               new Color(colour));
4856     }
4857
4858     if (al.hasSeqrep())
4859     {
4860       viewport.setColourByReferenceSeq(true);
4861       viewport.setDisplayReferenceSeq(true);
4862     }
4863
4864     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
4865
4866     if (view.getSequenceSetId() != null)
4867     {
4868       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4869
4870       viewport.setSequenceSetId(uniqueSeqSetId);
4871       if (av != null)
4872       {
4873         // propagate shared settings to this new view
4874         viewport.setHistoryList(av.getHistoryList());
4875         viewport.setRedoList(av.getRedoList());
4876       }
4877       else
4878       {
4879         viewportsAdded.put(uniqueSeqSetId, viewport);
4880       }
4881       // TODO: check if this method can be called repeatedly without
4882       // side-effects if alignpanel already registered.
4883       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4884     }
4885     // apply Hidden regions to view.
4886     if (hiddenSeqs != null)
4887     {
4888       for (int s = 0; s < JSEQ.size(); s++)
4889       {
4890         SequenceGroup hidden = new SequenceGroup();
4891         boolean isRepresentative = false;
4892         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
4893         {
4894           isRepresentative = true;
4895           SequenceI sequenceToHide = al
4896                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
4897           hidden.addSequence(sequenceToHide, false);
4898           // remove from hiddenSeqs list so we don't try to hide it twice
4899           hiddenSeqs.remove(sequenceToHide);
4900         }
4901         if (isRepresentative)
4902         {
4903           SequenceI representativeSequence = al.getSequenceAt(s);
4904           hidden.addSequence(representativeSequence, false);
4905           viewport.hideRepSequences(representativeSequence, hidden);
4906         }
4907       }
4908
4909       SequenceI[] hseqs = hiddenSeqs
4910               .toArray(new SequenceI[hiddenSeqs.size()]);
4911       viewport.hideSequence(hseqs);
4912
4913     }
4914     // recover view properties and display parameters
4915
4916     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4917     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
4918     final int pidThreshold = safeInt(view.getPidThreshold());
4919     viewport.setThreshold(pidThreshold);
4920
4921     viewport.setColourText(safeBoolean(view.isShowColourText()));
4922
4923     viewport.setConservationSelected(
4924             safeBoolean(view.isConservationSelected()));
4925     viewport.setIncrement(safeInt(view.getConsThreshold()));
4926     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
4927     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
4928     viewport.setFont(new Font(view.getFontName(),
4929             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
4930             true);
4931     ViewStyleI vs = viewport.getViewStyle();
4932     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4933     viewport.setViewStyle(vs);
4934     // TODO: allow custom charWidth/Heights to be restored by updating them
4935     // after setting font - which means set above to false
4936     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
4937     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
4938     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4939
4940     Boolean autocalcFirst = view.isShowAutocalcAbove();
4941     if (autocalcFirst != null)
4942     {
4943       af.setShowAutoCalculatedAbove(autocalcFirst.booleanValue());
4944     }
4945     String sortBy = view.getSortAnnotationsBy();
4946     if (sortBy != null)
4947     {
4948       try
4949       {
4950         viewport.setSortAnnotationsBy(
4951                 SequenceAnnotationOrder.valueOf(sortBy));
4952       } catch (IllegalArgumentException e)
4953       {
4954         Cache.log.error(
4955                 "Invalid annotation sort specifier in project: " + sortBy);
4956       }
4957     }
4958
4959     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
4960     viewport.setShowText(safeBoolean(view.isShowText()));
4961     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
4962     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
4963     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
4964     viewport.setShowUnconserved(view.isShowUnconserved());
4965     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
4966
4967     if (view.getViewName() != null)
4968     {
4969       viewport.setViewName(view.getViewName());
4970       af.setInitialTabVisible();
4971     }
4972     af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
4973             safeInt(view.getWidth()), safeInt(view.getHeight()));
4974     // startSeq set in af.alignPanel.updateLayout below
4975     af.alignPanel.updateLayout();
4976     ColourSchemeI cs = null;
4977     // apply colourschemes
4978     if (view.getBgColour() != null)
4979     {
4980       if (view.getBgColour().startsWith("ucs"))
4981       {
4982         cs = getUserColourScheme(jm, view.getBgColour());
4983       }
4984       else if (view.getBgColour().startsWith("Annotation"))
4985       {
4986         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
4987         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
4988
4989         // annpos
4990
4991       }
4992       else
4993       {
4994         cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
4995                 view.getBgColour());
4996       }
4997     }
4998
4999     /*
5000      * turn off 'alignment colour applies to all groups'
5001      * while restoring global colour scheme
5002      */
5003     viewport.setColourAppliesToAllGroups(false);
5004     viewport.setGlobalColourScheme(cs);
5005     viewport.getResidueShading().setThreshold(pidThreshold,
5006             view.isIgnoreGapsinConsensus());
5007     viewport.getResidueShading()
5008             .setConsensus(viewport.getSequenceConsensusHash());
5009     if (safeBoolean(view.isConservationSelected()) && cs != null)
5010     {
5011       viewport.getResidueShading()
5012               .setConservationInc(safeInt(view.getConsThreshold()));
5013     }
5014     af.changeColour(cs);
5015     viewport.setColourAppliesToAllGroups(true);
5016
5017     viewport
5018             .setShowSequenceFeatures(
5019                     safeBoolean(view.isShowSequenceFeatures()));
5020
5021     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
5022     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
5023     viewport.setFollowHighlight(view.isFollowHighlight());
5024     viewport.followSelection = view.isFollowSelection();
5025     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
5026     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
5027     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
5028     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
5029     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
5030     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
5031     viewport.setShowGroupConservation(view.isShowGroupConservation());
5032
5033     // recover feature settings
5034     if (jm.getFeatureSettings() != null)
5035     {
5036       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
5037               .getFeatureRenderer();
5038       FeaturesDisplayed fdi;
5039       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
5040       String[] renderOrder = new String[jm.getFeatureSettings()
5041               .getSetting().size()];
5042       Map<String, FeatureColourI> featureColours = new Hashtable<>();
5043       Map<String, Float> featureOrder = new Hashtable<>();
5044
5045       for (int fs = 0; fs < jm.getFeatureSettings()
5046               .getSetting().size(); fs++)
5047       {
5048         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
5049         String featureType = setting.getType();
5050
5051         /*
5052          * restore feature filters (if any)
5053          */
5054         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
5055                 .getMatcherSet();
5056         if (filters != null)
5057         {
5058           FeatureMatcherSetI filter = Jalview2XML
5059                   .parseFilter(featureType, filters);
5060           if (!filter.isEmpty())
5061           {
5062             fr.setFeatureFilter(featureType, filter);
5063           }
5064         }
5065
5066         /*
5067          * restore feature colour scheme
5068          */
5069         Color maxColour = new Color(setting.getColour());
5070         if (setting.getMincolour() != null)
5071         {
5072           /*
5073            * minColour is always set unless a simple colour
5074            * (including for colour by label though it doesn't use it)
5075            */
5076           Color minColour = new Color(setting.getMincolour().intValue());
5077           Color noValueColour = minColour;
5078           NoValueColour noColour = setting.getNoValueColour();
5079           if (noColour == NoValueColour.NONE)
5080           {
5081             noValueColour = null;
5082           }
5083           else if (noColour == NoValueColour.MAX)
5084           {
5085             noValueColour = maxColour;
5086           }
5087           float min = safeFloat(safeFloat(setting.getMin()));
5088           float max = setting.getMax() == null ? 1f
5089                   : setting.getMax().floatValue();
5090           FeatureColourI gc = new FeatureColour(maxColour, minColour,
5091                   maxColour,
5092                   noValueColour, min, max);
5093           if (setting.getAttributeName().size() > 0)
5094           {
5095             gc.setAttributeName(setting.getAttributeName().toArray(
5096                     new String[setting.getAttributeName().size()]));
5097           }
5098           if (setting.getThreshold() != null)
5099           {
5100             gc.setThreshold(setting.getThreshold().floatValue());
5101             int threshstate = safeInt(setting.getThreshstate());
5102             // -1 = None, 0 = Below, 1 = Above threshold
5103             if (threshstate == 0)
5104             {
5105               gc.setBelowThreshold(true);
5106             }
5107             else if (threshstate == 1)
5108             {
5109               gc.setAboveThreshold(true);
5110             }
5111           }
5112           gc.setAutoScaled(true); // default
5113           if (setting.isAutoScale() != null)
5114           {
5115             gc.setAutoScaled(setting.isAutoScale());
5116           }
5117           if (setting.isColourByLabel() != null)
5118           {
5119             gc.setColourByLabel(setting.isColourByLabel());
5120           }
5121           // and put in the feature colour table.
5122           featureColours.put(featureType, gc);
5123         }
5124         else
5125         {
5126           featureColours.put(featureType,
5127                   new FeatureColour(maxColour));
5128         }
5129         renderOrder[fs] = featureType;
5130         if (setting.getOrder() != null)
5131         {
5132           featureOrder.put(featureType, setting.getOrder().floatValue());
5133         }
5134         else
5135         {
5136           featureOrder.put(featureType, new Float(
5137                   fs / jm.getFeatureSettings().getSetting().size()));
5138         }
5139         if (safeBoolean(setting.isDisplay()))
5140         {
5141           fdi.setVisible(featureType);
5142         }
5143       }
5144       Map<String, Boolean> fgtable = new Hashtable<>();
5145       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
5146       {
5147         Group grp = jm.getFeatureSettings().getGroup().get(gs);
5148         fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
5149       }
5150       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5151       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
5152       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
5153       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5154               fgtable, featureColours, 1.0f, featureOrder);
5155       fr.transferSettings(frs);
5156     }
5157
5158     if (view.getHiddenColumns().size() > 0)
5159     {
5160       for (int c = 0; c < view.getHiddenColumns().size(); c++)
5161       {
5162         final HiddenColumns hc = view.getHiddenColumns().get(c);
5163         viewport.hideColumns(safeInt(hc.getStart()),
5164                 safeInt(hc.getEnd()) /* +1 */);
5165       }
5166     }
5167     if (view.getCalcIdParam() != null)
5168     {
5169       for (CalcIdParam calcIdParam : view.getCalcIdParam())
5170       {
5171         if (calcIdParam != null)
5172         {
5173           if (recoverCalcIdParam(calcIdParam, viewport))
5174           {
5175           }
5176           else
5177           {
5178             warn("Couldn't recover parameters for "
5179                     + calcIdParam.getCalcId());
5180           }
5181         }
5182       }
5183     }
5184     af.setMenusFromViewport(viewport);
5185     af.setTitle(view.getTitle());
5186     // TODO: we don't need to do this if the viewport is aready visible.
5187     /*
5188      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
5189      * has a 'cdna/protein complement' view, in which case save it in order to
5190      * populate a SplitFrame once all views have been read in.
5191      */
5192     String complementaryViewId = view.getComplementId();
5193     if (complementaryViewId == null)
5194     {
5195       Desktop.addInternalFrame(af, view.getTitle(),
5196               safeInt(view.getWidth()), safeInt(view.getHeight()));
5197       // recompute any autoannotation
5198       af.alignPanel.updateAnnotation(false, true);
5199       reorderAutoannotation(af, al, autoAlan);
5200       af.sortAnnotations(false);
5201       af.alignPanel.alignmentChanged();
5202     }
5203     else
5204     {
5205       splitFrameCandidates.put(view, af);
5206     }
5207     return af;
5208   }
5209
5210   /**
5211    * Reads saved data to restore Colour by Annotation settings
5212    * 
5213    * @param viewAnnColour
5214    * @param af
5215    * @param al
5216    * @param model
5217    * @param checkGroupAnnColour
5218    * @return
5219    */
5220   private ColourSchemeI constructAnnotationColour(
5221           AnnotationColourScheme viewAnnColour, AlignFrame af,
5222           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
5223   {
5224     boolean propagateAnnColour = false;
5225     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
5226             : al;
5227     if (checkGroupAnnColour && al.getGroups() != null
5228             && al.getGroups().size() > 0)
5229     {
5230       // pre 2.8.1 behaviour
5231       // check to see if we should transfer annotation colours
5232       propagateAnnColour = true;
5233       for (SequenceGroup sg : al.getGroups())
5234       {
5235         if (sg.getColourScheme() instanceof AnnotationColourGradient)
5236         {
5237           propagateAnnColour = false;
5238         }
5239       }
5240     }
5241
5242     /*
5243      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
5244      */
5245     String annotationId = viewAnnColour.getAnnotation();
5246     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
5247
5248     /*
5249      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
5250      */
5251     if (matchedAnnotation == null
5252             && annAlignment.getAlignmentAnnotation() != null)
5253     {
5254       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
5255       {
5256         if (annotationId
5257                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
5258         {
5259           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
5260           break;
5261         }
5262       }
5263     }
5264     if (matchedAnnotation == null)
5265     {
5266       System.err.println("Failed to match annotation colour scheme for "
5267               + annotationId);
5268       return null;
5269     }
5270     if (matchedAnnotation.getThreshold() == null)
5271     {
5272       matchedAnnotation.setThreshold(
5273               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5274                       "Threshold", Color.black));
5275     }
5276
5277     AnnotationColourGradient cs = null;
5278     if (viewAnnColour.getColourScheme().equals("None"))
5279     {
5280       cs = new AnnotationColourGradient(matchedAnnotation,
5281               new Color(safeInt(viewAnnColour.getMinColour())),
5282               new Color(safeInt(viewAnnColour.getMaxColour())),
5283               safeInt(viewAnnColour.getAboveThreshold()));
5284     }
5285     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5286     {
5287       cs = new AnnotationColourGradient(matchedAnnotation,
5288               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5289               safeInt(viewAnnColour.getAboveThreshold()));
5290     }
5291     else
5292     {
5293       cs = new AnnotationColourGradient(matchedAnnotation,
5294               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5295                       viewAnnColour.getColourScheme()),
5296               safeInt(viewAnnColour.getAboveThreshold()));
5297     }
5298
5299     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5300     boolean useOriginalColours = safeBoolean(
5301             viewAnnColour.isPredefinedColours());
5302     cs.setSeqAssociated(perSequenceOnly);
5303     cs.setPredefinedColours(useOriginalColours);
5304
5305     if (propagateAnnColour && al.getGroups() != null)
5306     {
5307       // Also use these settings for all the groups
5308       for (int g = 0; g < al.getGroups().size(); g++)
5309       {
5310         SequenceGroup sg = al.getGroups().get(g);
5311         if (sg.getGroupColourScheme() == null)
5312         {
5313           continue;
5314         }
5315
5316         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5317                 matchedAnnotation, sg.getColourScheme(),
5318                 safeInt(viewAnnColour.getAboveThreshold()));
5319         sg.setColourScheme(groupScheme);
5320         groupScheme.setSeqAssociated(perSequenceOnly);
5321         groupScheme.setPredefinedColours(useOriginalColours);
5322       }
5323     }
5324     return cs;
5325   }
5326
5327   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5328           List<JvAnnotRow> autoAlan)
5329   {
5330     // copy over visualization settings for autocalculated annotation in the
5331     // view
5332     if (al.getAlignmentAnnotation() != null)
5333     {
5334       /**
5335        * Kludge for magic autoannotation names (see JAL-811)
5336        */
5337       String[] magicNames = new String[] { "Consensus", "Quality",
5338           "Conservation" };
5339       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5340       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5341       for (String nm : magicNames)
5342       {
5343         visan.put(nm, nullAnnot);
5344       }
5345       for (JvAnnotRow auan : autoAlan)
5346       {
5347         visan.put(auan.template.label
5348                 + (auan.template.getCalcId() == null ? ""
5349                         : "\t" + auan.template.getCalcId()),
5350                 auan);
5351       }
5352       int hSize = al.getAlignmentAnnotation().length;
5353       List<JvAnnotRow> reorder = new ArrayList<>();
5354       // work through any autoCalculated annotation already on the view
5355       // removing it if it should be placed in a different location on the
5356       // annotation panel.
5357       List<String> remains = new ArrayList<>(visan.keySet());
5358       for (int h = 0; h < hSize; h++)
5359       {
5360         jalview.datamodel.AlignmentAnnotation jalan = al
5361                 .getAlignmentAnnotation()[h];
5362         if (jalan.autoCalculated)
5363         {
5364           String k;
5365           JvAnnotRow valan = visan.get(k = jalan.label);
5366           if (jalan.getCalcId() != null)
5367           {
5368             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5369           }
5370
5371           if (valan != null)
5372           {
5373             // delete the auto calculated row from the alignment
5374             al.deleteAnnotation(jalan, false);
5375             remains.remove(k);
5376             hSize--;
5377             h--;
5378             if (valan != nullAnnot)
5379             {
5380               if (jalan != valan.template)
5381               {
5382                 // newly created autoannotation row instance
5383                 // so keep a reference to the visible annotation row
5384                 // and copy over all relevant attributes
5385                 if (valan.template.graphHeight >= 0)
5386
5387                 {
5388                   jalan.graphHeight = valan.template.graphHeight;
5389                 }
5390                 jalan.visible = valan.template.visible;
5391               }
5392               reorder.add(new JvAnnotRow(valan.order, jalan));
5393             }
5394           }
5395         }
5396       }
5397       // Add any (possibly stale) autocalculated rows that were not appended to
5398       // the view during construction
5399       for (String other : remains)
5400       {
5401         JvAnnotRow othera = visan.get(other);
5402         if (othera != nullAnnot && othera.template.getCalcId() != null
5403                 && othera.template.getCalcId().length() > 0)
5404         {
5405           reorder.add(othera);
5406         }
5407       }
5408       // now put the automatic annotation in its correct place
5409       int s = 0, srt[] = new int[reorder.size()];
5410       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5411       for (JvAnnotRow jvar : reorder)
5412       {
5413         rws[s] = jvar;
5414         srt[s++] = jvar.order;
5415       }
5416       reorder.clear();
5417       jalview.util.QuickSort.sort(srt, rws);
5418       // and re-insert the annotation at its correct position
5419       for (JvAnnotRow jvar : rws)
5420       {
5421         al.addAnnotation(jvar.template, jvar.order);
5422       }
5423       af.alignPanel.adjustAnnotationHeight();
5424     }
5425   }
5426
5427   Hashtable skipList = null;
5428
5429   /**
5430    * TODO remove this method
5431    * 
5432    * @param view
5433    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5434    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5435    *         throw new Error("Implementation Error. No skipList defined for this
5436    *         Jalview2XML instance."); } return (AlignFrame)
5437    *         skipList.get(view.getSequenceSetId()); }
5438    */
5439
5440   /**
5441    * Check if the Jalview view contained in object should be skipped or not.
5442    * 
5443    * @param object
5444    * @return true if view's sequenceSetId is a key in skipList
5445    */
5446   private boolean skipViewport(JalviewModel object)
5447   {
5448     if (skipList == null)
5449     {
5450       return false;
5451     }
5452     String id = object.getViewport().get(0).getSequenceSetId();
5453     if (skipList.containsKey(id))
5454     {
5455       if (Cache.log != null && Cache.log.isDebugEnabled())
5456       {
5457         Cache.log.debug("Skipping seuqence set id " + id);
5458       }
5459       return true;
5460     }
5461     return false;
5462   }
5463
5464   public void addToSkipList(AlignFrame af)
5465   {
5466     if (skipList == null)
5467     {
5468       skipList = new Hashtable();
5469     }
5470     skipList.put(af.getViewport().getSequenceSetId(), af);
5471   }
5472
5473   public void clearSkipList()
5474   {
5475     if (skipList != null)
5476     {
5477       skipList.clear();
5478       skipList = null;
5479     }
5480   }
5481
5482   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5483           boolean ignoreUnrefed, String uniqueSeqSetId)
5484   {
5485     jalview.datamodel.AlignmentI ds = getDatasetFor(
5486             vamsasSet.getDatasetId());
5487     AlignmentI xtant_ds = ds;
5488     if (xtant_ds == null)
5489     {
5490       // good chance we are about to create a new dataset, but check if we've
5491       // seen some of the dataset sequence IDs before.
5492       // TODO: skip this check if we are working with project generated by
5493       // version 2.11 or later
5494       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5495       if (xtant_ds != null)
5496       {
5497         ds = xtant_ds;
5498         addDatasetRef(vamsasSet.getDatasetId(), ds);
5499       }
5500     }
5501     Vector dseqs = null;
5502     if (!ignoreUnrefed)
5503     {
5504       // recovering an alignment View
5505       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5506       if (seqSetDS != null)
5507       {
5508         if (ds != null && ds != seqSetDS)
5509         {
5510           warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
5511                   + " - CDS/Protein crossreference data may be lost");
5512           if (xtant_ds != null)
5513           {
5514             // This can only happen if the unique sequence set ID was bound to a
5515             // dataset that did not contain any of the sequences in the view
5516             // currently being restored.
5517             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.");
5518           }
5519         }
5520         ds = seqSetDS;
5521         addDatasetRef(vamsasSet.getDatasetId(), ds);
5522       }
5523     }
5524     if (ds == null)
5525     {
5526       // try even harder to restore dataset
5527       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5528       // create a list of new dataset sequences
5529       dseqs = new Vector();
5530     }
5531     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5532     {
5533       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5534       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5535     }
5536     // create a new dataset
5537     if (ds == null)
5538     {
5539       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5540       dseqs.copyInto(dsseqs);
5541       ds = new jalview.datamodel.Alignment(dsseqs);
5542       debug("Created new dataset " + vamsasSet.getDatasetId()
5543               + " for alignment " + System.identityHashCode(al));
5544       addDatasetRef(vamsasSet.getDatasetId(), ds);
5545     }
5546     // set the dataset for the newly imported alignment.
5547     if (al.getDataset() == null && !ignoreUnrefed)
5548     {
5549       al.setDataset(ds);
5550       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5551       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5552     }
5553     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5554   }
5555
5556   /**
5557    * XML dataset sequence ID to materialised dataset reference
5558    */
5559   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5560
5561   /**
5562    * @return the first materialised dataset reference containing a dataset
5563    *         sequence referenced in the given view
5564    * @param list
5565    *          - sequences from the view
5566    */
5567   AlignmentI checkIfHasDataset(List<Sequence> list)
5568   {
5569     for (Sequence restoredSeq : list)
5570     {
5571       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5572       if (datasetFor != null)
5573       {
5574         return datasetFor;
5575       }
5576     }
5577     return null;
5578   }
5579
5580   /**
5581    * Register ds as the containing dataset for the dataset sequences referenced
5582    * by sequences in list
5583    * 
5584    * @param list
5585    *          - sequences in a view
5586    * @param ds
5587    */
5588   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
5589   {
5590     for (Sequence restoredSeq : list)
5591     {
5592       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
5593       if (prevDS != null && prevDS != ds)
5594       {
5595         warn("Dataset sequence appears in many datasets: "
5596                 + restoredSeq.getDsseqid());
5597         // TODO: try to merge!
5598       }
5599     }
5600   }
5601   /**
5602    * 
5603    * @param vamsasSeq
5604    *          sequence definition to create/merge dataset sequence for
5605    * @param ds
5606    *          dataset alignment
5607    * @param dseqs
5608    *          vector to add new dataset sequence to
5609    * @param ignoreUnrefed
5610    *          - when true, don't create new sequences from vamsasSeq if it's id
5611    *          doesn't already have an asssociated Jalview sequence.
5612    * @param vseqpos
5613    *          - used to reorder the sequence in the alignment according to the
5614    *          vamsasSeq array ordering, to preserve ordering of dataset
5615    */
5616   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5617           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5618   {
5619     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5620     // xRef Codon Maps
5621     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5622     boolean reorder = false;
5623     SequenceI dsq = null;
5624     if (sq != null && sq.getDatasetSequence() != null)
5625     {
5626       dsq = sq.getDatasetSequence();
5627     }
5628     else
5629     {
5630       reorder = true;
5631     }
5632     if (sq == null && ignoreUnrefed)
5633     {
5634       return;
5635     }
5636     String sqid = vamsasSeq.getDsseqid();
5637     if (dsq == null)
5638     {
5639       // need to create or add a new dataset sequence reference to this sequence
5640       if (sqid != null)
5641       {
5642         dsq = seqRefIds.get(sqid);
5643       }
5644       // check again
5645       if (dsq == null)
5646       {
5647         // make a new dataset sequence
5648         dsq = sq.createDatasetSequence();
5649         if (sqid == null)
5650         {
5651           // make up a new dataset reference for this sequence
5652           sqid = seqHash(dsq);
5653         }
5654         dsq.setVamsasId(uniqueSetSuffix + sqid);
5655         seqRefIds.put(sqid, dsq);
5656         if (ds == null)
5657         {
5658           if (dseqs != null)
5659           {
5660             dseqs.addElement(dsq);
5661           }
5662         }
5663         else
5664         {
5665           ds.addSequence(dsq);
5666         }
5667       }
5668       else
5669       {
5670         if (sq != dsq)
5671         { // make this dataset sequence sq's dataset sequence
5672           sq.setDatasetSequence(dsq);
5673           // and update the current dataset alignment
5674           if (ds == null)
5675           {
5676             if (dseqs != null)
5677             {
5678               if (!dseqs.contains(dsq))
5679               {
5680                 dseqs.add(dsq);
5681               }
5682             }
5683             else
5684             {
5685               if (ds.findIndex(dsq) < 0)
5686               {
5687                 ds.addSequence(dsq);
5688               }
5689             }
5690           }
5691         }
5692       }
5693     }
5694     // TODO: refactor this as a merge dataset sequence function
5695     // now check that sq (the dataset sequence) sequence really is the union of
5696     // all references to it
5697     // boolean pre = sq.getStart() < dsq.getStart();
5698     // boolean post = sq.getEnd() > dsq.getEnd();
5699     // if (pre || post)
5700     if (sq != dsq)
5701     {
5702       // StringBuffer sb = new StringBuffer();
5703       String newres = jalview.analysis.AlignSeq.extractGaps(
5704               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5705       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5706               && newres.length() > dsq.getLength())
5707       {
5708         // Update with the longer sequence.
5709         synchronized (dsq)
5710         {
5711           /*
5712            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5713            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5714            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5715            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5716            */
5717           dsq.setSequence(newres);
5718         }
5719         // TODO: merges will never happen if we 'know' we have the real dataset
5720         // sequence - this should be detected when id==dssid
5721         System.err.println(
5722                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5723         // + (pre ? "prepended" : "") + " "
5724         // + (post ? "appended" : ""));
5725       }
5726     }
5727     else
5728     {
5729       // sequence refs are identical. We may need to update the existing dataset
5730       // alignment with this one, though.
5731       if (ds != null && dseqs == null)
5732       {
5733         int opos = ds.findIndex(dsq);
5734         SequenceI tseq = null;
5735         if (opos != -1 && vseqpos != opos)
5736         {
5737           // remove from old position
5738           ds.deleteSequence(dsq);
5739         }
5740         if (vseqpos < ds.getHeight())
5741         {
5742           if (vseqpos != opos)
5743           {
5744             // save sequence at destination position
5745             tseq = ds.getSequenceAt(vseqpos);
5746             ds.replaceSequenceAt(vseqpos, dsq);
5747             ds.addSequence(tseq);
5748           }
5749         }
5750         else
5751         {
5752           ds.addSequence(dsq);
5753         }
5754       }
5755     }
5756   }
5757
5758   /*
5759    * TODO use AlignmentI here and in related methods - needs
5760    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5761    */
5762   Hashtable<String, AlignmentI> datasetIds = null;
5763
5764   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5765
5766   private AlignmentI getDatasetFor(String datasetId)
5767   {
5768     if (datasetIds == null)
5769     {
5770       datasetIds = new Hashtable<>();
5771       return null;
5772     }
5773     if (datasetIds.containsKey(datasetId))
5774     {
5775       return datasetIds.get(datasetId);
5776     }
5777     return null;
5778   }
5779
5780   private void addDatasetRef(String datasetId, AlignmentI dataset)
5781   {
5782     if (datasetIds == null)
5783     {
5784       datasetIds = new Hashtable<>();
5785     }
5786     datasetIds.put(datasetId, dataset);
5787   }
5788
5789   /**
5790    * make a new dataset ID for this jalview dataset alignment
5791    * 
5792    * @param dataset
5793    * @return
5794    */
5795   private String getDatasetIdRef(AlignmentI dataset)
5796   {
5797     if (dataset.getDataset() != null)
5798     {
5799       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5800     }
5801     String datasetId = makeHashCode(dataset, null);
5802     if (datasetId == null)
5803     {
5804       // make a new datasetId and record it
5805       if (dataset2Ids == null)
5806       {
5807         dataset2Ids = new IdentityHashMap<>();
5808       }
5809       else
5810       {
5811         datasetId = dataset2Ids.get(dataset);
5812       }
5813       if (datasetId == null)
5814       {
5815         datasetId = "ds" + dataset2Ids.size() + 1;
5816         dataset2Ids.put(dataset, datasetId);
5817       }
5818     }
5819     return datasetId;
5820   }
5821
5822   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5823   {
5824     for (int d = 0; d < sequence.getDBRef().size(); d++)
5825     {
5826       DBRef dr = sequence.getDBRef().get(d);
5827       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5828               dr.getSource(), dr.getVersion(), dr.getAccessionId());
5829       if (dr.getMapping() != null)
5830       {
5831         entry.setMap(addMapping(dr.getMapping()));
5832       }
5833       datasetSequence.addDBRef(entry);
5834     }
5835   }
5836
5837   private jalview.datamodel.Mapping addMapping(Mapping m)
5838   {
5839     SequenceI dsto = null;
5840     // Mapping m = dr.getMapping();
5841     int fr[] = new int[m.getMapListFrom().size() * 2];
5842     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
5843     for (int _i = 0; from.hasNext(); _i += 2)
5844     {
5845       MapListFrom mf = from.next();
5846       fr[_i] = mf.getStart();
5847       fr[_i + 1] = mf.getEnd();
5848     }
5849     int fto[] = new int[m.getMapListTo().size() * 2];
5850     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
5851     for (int _i = 0; to.hasNext(); _i += 2)
5852     {
5853       MapListTo mf = to.next();
5854       fto[_i] = mf.getStart();
5855       fto[_i + 1] = mf.getEnd();
5856     }
5857     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5858             fto, m.getMapFromUnit().intValue(),
5859             m.getMapToUnit().intValue());
5860
5861     /*
5862      * (optional) choice of dseqFor or Sequence
5863      */
5864     if (m.getDseqFor() != null)
5865     {
5866       String dsfor = m.getDseqFor();
5867       if (seqRefIds.containsKey(dsfor))
5868       {
5869         /*
5870          * recover from hash
5871          */
5872         jmap.setTo(seqRefIds.get(dsfor));
5873       }
5874       else
5875       {
5876         frefedSequence.add(newMappingRef(dsfor, jmap));
5877       }
5878     }
5879     else if (m.getSequence() != null)
5880     {
5881       /*
5882        * local sequence definition
5883        */
5884       Sequence ms = m.getSequence();
5885       SequenceI djs = null;
5886       String sqid = ms.getDsseqid();
5887       if (sqid != null && sqid.length() > 0)
5888       {
5889         /*
5890          * recover dataset sequence
5891          */
5892         djs = seqRefIds.get(sqid);
5893       }
5894       else
5895       {
5896         System.err.println(
5897                 "Warning - making up dataset sequence id for DbRef sequence map reference");
5898         sqid = ((Object) ms).toString(); // make up a new hascode for
5899         // undefined dataset sequence hash
5900         // (unlikely to happen)
5901       }
5902
5903       if (djs == null)
5904       {
5905         /**
5906          * make a new dataset sequence and add it to refIds hash
5907          */
5908         djs = new jalview.datamodel.Sequence(ms.getName(),
5909                 ms.getSequence());
5910         djs.setStart(jmap.getMap().getToLowest());
5911         djs.setEnd(jmap.getMap().getToHighest());
5912         djs.setVamsasId(uniqueSetSuffix + sqid);
5913         jmap.setTo(djs);
5914         incompleteSeqs.put(sqid, djs);
5915         seqRefIds.put(sqid, djs);
5916
5917       }
5918       jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5919       addDBRefs(djs, ms);
5920
5921     }
5922
5923     return jmap;
5924   }
5925
5926   /**
5927    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
5928    * view as XML (but not to file), and then reloading it
5929    * 
5930    * @param ap
5931    * @return
5932    */
5933   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
5934   {
5935     initSeqRefs();
5936     JalviewModel jm = saveState(ap, null, null, null);
5937
5938     addDatasetRef(
5939             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
5940             ap.getAlignment().getDataset());
5941
5942     uniqueSetSuffix = "";
5943     // jm.getJalviewModelSequence().getViewport(0).setId(null);
5944     jm.getViewport().get(0).setId(null);
5945     // we don't overwrite the view we just copied
5946
5947     if (this.frefedSequence == null)
5948     {
5949       frefedSequence = new Vector<>();
5950     }
5951
5952     viewportsAdded.clear();
5953
5954     AlignFrame af = loadFromObject(jm, null, false, null);
5955     af.getAlignPanels().clear();
5956     af.closeMenuItem_actionPerformed(true);
5957
5958     /*
5959      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5960      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5961      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5962      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5963      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5964      */
5965
5966     return af.alignPanel;
5967   }
5968
5969   private Hashtable jvids2vobj;
5970
5971   private void warn(String msg)
5972   {
5973     warn(msg, null);
5974   }
5975
5976   private void warn(String msg, Exception e)
5977   {
5978     if (Cache.log != null)
5979     {
5980       if (e != null)
5981       {
5982         Cache.log.warn(msg, e);
5983       }
5984       else
5985       {
5986         Cache.log.warn(msg);
5987       }
5988     }
5989     else
5990     {
5991       System.err.println("Warning: " + msg);
5992       if (e != null)
5993       {
5994         e.printStackTrace();
5995       }
5996     }
5997   }
5998
5999   private void debug(String string)
6000   {
6001     debug(string, null);
6002   }
6003
6004   private void debug(String msg, Exception e)
6005   {
6006     if (Cache.log != null)
6007     {
6008       if (e != null)
6009       {
6010         Cache.log.debug(msg, e);
6011       }
6012       else
6013       {
6014         Cache.log.debug(msg);
6015       }
6016     }
6017     else
6018     {
6019       System.err.println("Warning: " + msg);
6020       if (e != null)
6021       {
6022         e.printStackTrace();
6023       }
6024     }
6025   }
6026
6027   /**
6028    * set the object to ID mapping tables used to write/recover objects and XML
6029    * ID strings for the jalview project. If external tables are provided then
6030    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
6031    * object goes out of scope. - also populates the datasetIds hashtable with
6032    * alignment objects containing dataset sequences
6033    * 
6034    * @param vobj2jv
6035    *          Map from ID strings to jalview datamodel
6036    * @param jv2vobj
6037    *          Map from jalview datamodel to ID strings
6038    * 
6039    * 
6040    */
6041   public void setObjectMappingTables(Hashtable vobj2jv,
6042           IdentityHashMap jv2vobj)
6043   {
6044     this.jv2vobj = jv2vobj;
6045     this.vobj2jv = vobj2jv;
6046     Iterator ds = jv2vobj.keySet().iterator();
6047     String id;
6048     while (ds.hasNext())
6049     {
6050       Object jvobj = ds.next();
6051       id = jv2vobj.get(jvobj).toString();
6052       if (jvobj instanceof jalview.datamodel.Alignment)
6053       {
6054         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
6055         {
6056           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
6057         }
6058       }
6059       else if (jvobj instanceof jalview.datamodel.Sequence)
6060       {
6061         // register sequence object so the XML parser can recover it.
6062         if (seqRefIds == null)
6063         {
6064           seqRefIds = new HashMap<>();
6065         }
6066         if (seqsToIds == null)
6067         {
6068           seqsToIds = new IdentityHashMap<>();
6069         }
6070         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
6071         seqsToIds.put((SequenceI) jvobj, id);
6072       }
6073       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
6074       {
6075         String anid;
6076         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
6077         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
6078         if (jvann.annotationId == null)
6079         {
6080           jvann.annotationId = anid;
6081         }
6082         if (!jvann.annotationId.equals(anid))
6083         {
6084           // TODO verify that this is the correct behaviour
6085           this.warn("Overriding Annotation ID for " + anid
6086                   + " from different id : " + jvann.annotationId);
6087           jvann.annotationId = anid;
6088         }
6089       }
6090       else if (jvobj instanceof String)
6091       {
6092         if (jvids2vobj == null)
6093         {
6094           jvids2vobj = new Hashtable();
6095           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
6096         }
6097       }
6098       else
6099       {
6100         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
6101       }
6102     }
6103   }
6104
6105   /**
6106    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
6107    * objects created from the project archive. If string is null (default for
6108    * construction) then suffix will be set automatically.
6109    * 
6110    * @param string
6111    */
6112   public void setUniqueSetSuffix(String string)
6113   {
6114     uniqueSetSuffix = string;
6115
6116   }
6117
6118   /**
6119    * uses skipList2 as the skipList for skipping views on sequence sets
6120    * associated with keys in the skipList
6121    * 
6122    * @param skipList2
6123    */
6124   public void setSkipList(Hashtable skipList2)
6125   {
6126     skipList = skipList2;
6127   }
6128
6129   /**
6130    * Reads the jar entry of given name and returns its contents, or null if the
6131    * entry is not found.
6132    * 
6133    * @param jprovider
6134    * @param jarEntryName
6135    * @return
6136    */
6137   protected String readJarEntry(jarInputStreamProvider jprovider,
6138           String jarEntryName)
6139   {
6140     String result = null;
6141     BufferedReader in = null;
6142
6143     try
6144     {
6145       /*
6146        * Reopen the jar input stream and traverse its entries to find a matching
6147        * name
6148        */
6149       JarInputStream jin = jprovider.getJarInputStream();
6150       JarEntry entry = null;
6151       do
6152       {
6153         entry = jin.getNextJarEntry();
6154       } while (entry != null && !entry.getName().equals(jarEntryName));
6155
6156       if (entry != null)
6157       {
6158         StringBuilder out = new StringBuilder(256);
6159         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
6160         String data;
6161
6162         while ((data = in.readLine()) != null)
6163         {
6164           out.append(data);
6165         }
6166         result = out.toString();
6167       }
6168       else
6169       {
6170         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
6171       }
6172     } catch (Exception ex)
6173     {
6174       ex.printStackTrace();
6175     } finally
6176     {
6177       if (in != null)
6178       {
6179         try
6180         {
6181           in.close();
6182         } catch (IOException e)
6183         {
6184           // ignore
6185         }
6186       }
6187     }
6188
6189     return result;
6190   }
6191
6192   /**
6193    * Returns an incrementing counter (0, 1, 2...)
6194    * 
6195    * @return
6196    */
6197   private synchronized int nextCounter()
6198   {
6199     return counter++;
6200   }
6201
6202   /**
6203    * Loads any saved PCA viewers
6204    * 
6205    * @param jms
6206    * @param ap
6207    */
6208   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
6209   {
6210     try
6211     {
6212       List<PcaViewer> pcaviewers = model.getPcaViewer();
6213       for (PcaViewer viewer : pcaviewers)
6214       {
6215         String modelName = viewer.getScoreModelName();
6216         SimilarityParamsI params = new SimilarityParams(
6217                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6218                 viewer.isIncludeGaps(),
6219                 viewer.isDenominateByShortestLength());
6220
6221         /*
6222          * create the panel (without computing the PCA)
6223          */
6224         PCAPanel panel = new PCAPanel(ap, modelName, params);
6225
6226         panel.setTitle(viewer.getTitle());
6227         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6228                 viewer.getWidth(), viewer.getHeight()));
6229
6230         boolean showLabels = viewer.isShowLabels();
6231         panel.setShowLabels(showLabels);
6232         panel.getRotatableCanvas().setShowLabels(showLabels);
6233         panel.getRotatableCanvas()
6234                 .setBgColour(new Color(viewer.getBgColour()));
6235         panel.getRotatableCanvas()
6236                 .setApplyToAllViews(viewer.isLinkToAllViews());
6237
6238         /*
6239          * load PCA output data
6240          */
6241         ScoreModelI scoreModel = ScoreModels.getInstance()
6242                 .getScoreModel(modelName, ap);
6243         PCA pca = new PCA(null, scoreModel, params);
6244         PcaDataType pcaData = viewer.getPcaData();
6245
6246         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
6247         pca.setPairwiseScores(pairwise);
6248
6249         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
6250         pca.setTridiagonal(triDiag);
6251
6252         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
6253         pca.setEigenmatrix(result);
6254
6255         panel.getPcaModel().setPCA(pca);
6256
6257         /*
6258          * we haven't saved the input data! (JAL-2647 to do)
6259          */
6260         panel.setInputData(null);
6261
6262         /*
6263          * add the sequence points for the PCA display
6264          */
6265         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6266         for (SequencePoint sp : viewer.getSequencePoint())
6267         {
6268           String seqId = sp.getSequenceRef();
6269           SequenceI seq = seqRefIds.get(seqId);
6270           if (seq == null)
6271           {
6272             throw new IllegalStateException(
6273                     "Unmatched seqref for PCA: " + seqId);
6274           }
6275           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6276           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6277                   seq, pt);
6278           seqPoints.add(seqPoint);
6279         }
6280         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6281
6282         /*
6283          * set min-max ranges and scale after setPoints (which recomputes them)
6284          */
6285         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6286         SeqPointMin spMin = viewer.getSeqPointMin();
6287         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6288             spMin.getZPos() };
6289         SeqPointMax spMax = viewer.getSeqPointMax();
6290         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6291             spMax.getZPos() };
6292         panel.getRotatableCanvas().setSeqMinMax(min, max);
6293
6294         // todo: hold points list in PCAModel only
6295         panel.getPcaModel().setSequencePoints(seqPoints);
6296
6297         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6298         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6299         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6300
6301         // is this duplication needed?
6302         panel.setTop(seqPoints.size() - 1);
6303         panel.getPcaModel().setTop(seqPoints.size() - 1);
6304
6305         /*
6306          * add the axes' end points for the display
6307          */
6308         for (int i = 0; i < 3; i++)
6309         {
6310           Axis axis = viewer.getAxis().get(i);
6311           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6312                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6313         }
6314
6315         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6316                 "label.calc_title", "PCA", modelName), 475, 450);
6317       }
6318     } catch (Exception ex)
6319     {
6320       Cache.log.error("Error loading PCA: " + ex.toString());
6321     }
6322   }
6323
6324   /**
6325    * Populates an XML model of the feature colour scheme for one feature type
6326    * 
6327    * @param featureType
6328    * @param fcol
6329    * @return
6330    */
6331   public static Colour marshalColour(
6332           String featureType, FeatureColourI fcol)
6333   {
6334     Colour col = new Colour();
6335     if (fcol.isSimpleColour())
6336     {
6337       col.setRGB(Format.getHexString(fcol.getColour()));
6338     }
6339     else
6340     {
6341       col.setRGB(Format.getHexString(fcol.getMaxColour()));
6342       col.setMin(fcol.getMin());
6343       col.setMax(fcol.getMax());
6344       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
6345       col.setAutoScale(fcol.isAutoScaled());
6346       col.setThreshold(fcol.getThreshold());
6347       col.setColourByLabel(fcol.isColourByLabel());
6348       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
6349               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
6350                       : ThresholdType.NONE));
6351       if (fcol.isColourByAttribute())
6352       {
6353         final String[] attName = fcol.getAttributeName();
6354         col.getAttributeName().add(attName[0]);
6355         if (attName.length > 1)
6356         {
6357           col.getAttributeName().add(attName[1]);
6358         }
6359       }
6360       Color noColour = fcol.getNoColour();
6361       if (noColour == null)
6362       {
6363         col.setNoValueColour(NoValueColour.NONE);
6364       }
6365       else if (noColour == fcol.getMaxColour())
6366       {
6367         col.setNoValueColour(NoValueColour.MAX);
6368       }
6369       else
6370       {
6371         col.setNoValueColour(NoValueColour.MIN);
6372       }
6373     }
6374     col.setName(featureType);
6375     return col;
6376   }
6377
6378   /**
6379    * Populates an XML model of the feature filter(s) for one feature type
6380    * 
6381    * @param firstMatcher
6382    *          the first (or only) match condition)
6383    * @param filter
6384    *          remaining match conditions (if any)
6385    * @param and
6386    *          if true, conditions are and-ed, else or-ed
6387    */
6388   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
6389           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
6390           boolean and)
6391   {
6392     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
6393   
6394     if (filters.hasNext())
6395     {
6396       /*
6397        * compound matcher
6398        */
6399       CompoundMatcher compound = new CompoundMatcher();
6400       compound.setAnd(and);
6401       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
6402               firstMatcher, Collections.emptyIterator(), and);
6403       // compound.addMatcherSet(matcher1);
6404       compound.getMatcherSet().add(matcher1);
6405       FeatureMatcherI nextMatcher = filters.next();
6406       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
6407               nextMatcher, filters, and);
6408       // compound.addMatcherSet(matcher2);
6409       compound.getMatcherSet().add(matcher2);
6410       result.setCompoundMatcher(compound);
6411     }
6412     else
6413     {
6414       /*
6415        * single condition matcher
6416        */
6417       // MatchCondition matcherModel = new MatchCondition();
6418       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
6419       matcherModel.setCondition(
6420               firstMatcher.getMatcher().getCondition().getStableName());
6421       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
6422       if (firstMatcher.isByAttribute())
6423       {
6424         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
6425         // matcherModel.setAttributeName(firstMatcher.getAttribute());
6426         String[] attName = firstMatcher.getAttribute();
6427         matcherModel.getAttributeName().add(attName[0]); // attribute
6428         if (attName.length > 1)
6429         {
6430           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
6431         }
6432       }
6433       else if (firstMatcher.isByLabel())
6434       {
6435         matcherModel.setBy(FilterBy.BY_LABEL);
6436       }
6437       else if (firstMatcher.isByScore())
6438       {
6439         matcherModel.setBy(FilterBy.BY_SCORE);
6440       }
6441       result.setMatchCondition(matcherModel);
6442     }
6443   
6444     return result;
6445   }
6446
6447   /**
6448    * Loads one XML model of a feature filter to a Jalview object
6449    * 
6450    * @param featureType
6451    * @param matcherSetModel
6452    * @return
6453    */
6454   public static FeatureMatcherSetI parseFilter(
6455           String featureType,
6456           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
6457   {
6458     FeatureMatcherSetI result = new FeatureMatcherSet();
6459     try
6460     {
6461       parseFilterConditions(result, matcherSetModel, true);
6462     } catch (IllegalStateException e)
6463     {
6464       // mixing AND and OR conditions perhaps
6465       System.err.println(
6466               String.format("Error reading filter conditions for '%s': %s",
6467                       featureType, e.getMessage()));
6468       // return as much as was parsed up to the error
6469     }
6470   
6471     return result;
6472   }
6473
6474   /**
6475    * Adds feature match conditions to matcherSet as unmarshalled from XML
6476    * (possibly recursively for compound conditions)
6477    * 
6478    * @param matcherSet
6479    * @param matcherSetModel
6480    * @param and
6481    *          if true, multiple conditions are AND-ed, else they are OR-ed
6482    * @throws IllegalStateException
6483    *           if AND and OR conditions are mixed
6484    */
6485   protected static void parseFilterConditions(
6486           FeatureMatcherSetI matcherSet,
6487           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
6488           boolean and)
6489   {
6490     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
6491             .getMatchCondition();
6492     if (mc != null)
6493     {
6494       /*
6495        * single condition
6496        */
6497       FilterBy filterBy = mc.getBy();
6498       Condition cond = Condition.fromString(mc.getCondition());
6499       String pattern = mc.getValue();
6500       FeatureMatcherI matchCondition = null;
6501       if (filterBy == FilterBy.BY_LABEL)
6502       {
6503         matchCondition = FeatureMatcher.byLabel(cond, pattern);
6504       }
6505       else if (filterBy == FilterBy.BY_SCORE)
6506       {
6507         matchCondition = FeatureMatcher.byScore(cond, pattern);
6508   
6509       }
6510       else if (filterBy == FilterBy.BY_ATTRIBUTE)
6511       {
6512         final List<String> attributeName = mc.getAttributeName();
6513         String[] attNames = attributeName
6514                 .toArray(new String[attributeName.size()]);
6515         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
6516                 attNames);
6517       }
6518   
6519       /*
6520        * note this throws IllegalStateException if AND-ing to a 
6521        * previously OR-ed compound condition, or vice versa
6522        */
6523       if (and)
6524       {
6525         matcherSet.and(matchCondition);
6526       }
6527       else
6528       {
6529         matcherSet.or(matchCondition);
6530       }
6531     }
6532     else
6533     {
6534       /*
6535        * compound condition
6536        */
6537       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
6538               .getCompoundMatcher().getMatcherSet();
6539       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
6540       if (matchers.size() == 2)
6541       {
6542         parseFilterConditions(matcherSet, matchers.get(0), anded);
6543         parseFilterConditions(matcherSet, matchers.get(1), anded);
6544       }
6545       else
6546       {
6547         System.err.println("Malformed compound filter condition");
6548       }
6549     }
6550   }
6551
6552   /**
6553    * Loads one XML model of a feature colour to a Jalview object
6554    * 
6555    * @param colourModel
6556    * @return
6557    */
6558   public static FeatureColourI parseColour(Colour colourModel)
6559   {
6560     FeatureColourI colour = null;
6561   
6562     if (colourModel.getMax() != null)
6563     {
6564       Color mincol = null;
6565       Color maxcol = null;
6566       Color noValueColour = null;
6567   
6568       try
6569       {
6570         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
6571         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6572       } catch (Exception e)
6573       {
6574         Cache.log.warn("Couldn't parse out graduated feature color.", e);
6575       }
6576   
6577       NoValueColour noCol = colourModel.getNoValueColour();
6578       if (noCol == NoValueColour.MIN)
6579       {
6580         noValueColour = mincol;
6581       }
6582       else if (noCol == NoValueColour.MAX)
6583       {
6584         noValueColour = maxcol;
6585       }
6586   
6587       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
6588               safeFloat(colourModel.getMin()),
6589               safeFloat(colourModel.getMax()));
6590       final List<String> attributeName = colourModel.getAttributeName();
6591       String[] attributes = attributeName
6592               .toArray(new String[attributeName.size()]);
6593       if (attributes != null && attributes.length > 0)
6594       {
6595         colour.setAttributeName(attributes);
6596       }
6597       if (colourModel.isAutoScale() != null)
6598       {
6599         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
6600       }
6601       if (colourModel.isColourByLabel() != null)
6602       {
6603         colour.setColourByLabel(
6604                 colourModel.isColourByLabel().booleanValue());
6605       }
6606       if (colourModel.getThreshold() != null)
6607       {
6608         colour.setThreshold(colourModel.getThreshold().floatValue());
6609       }
6610       ThresholdType ttyp = colourModel.getThreshType();
6611       if (ttyp == ThresholdType.ABOVE)
6612       {
6613         colour.setAboveThreshold(true);
6614       }
6615       else if (ttyp == ThresholdType.BELOW)
6616       {
6617         colour.setBelowThreshold(true);
6618       }
6619     }
6620     else
6621     {
6622       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6623       colour = new FeatureColour(color);
6624     }
6625   
6626     return colour;
6627   }
6628 }