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