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