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