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