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