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