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