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