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