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