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