JAL-2069 ensure threshline instantiated to avoid NPE
[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.getCurrentTree() != 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.getCurrentTree() == 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       af.setMenusForViewport();
2271
2272     } catch (MalformedURLException e)
2273     {
2274       errorMessage = "Invalid URL format for '" + file + "'";
2275       reportErrors();
2276     } finally
2277     {
2278       try
2279       {
2280         SwingUtilities.invokeAndWait(new Runnable()
2281         {
2282           @Override
2283           public void run()
2284           {
2285             setLoadingFinishedForNewStructureViewers();
2286           };
2287         });
2288       } catch (Exception x)
2289       {
2290         System.err.println("Error loading alignment: " + x.getMessage());
2291       }
2292     }
2293     return af;
2294   }
2295
2296   private jarInputStreamProvider createjarInputStreamProvider(
2297           final String file) throws MalformedURLException
2298   {
2299     URL url = null;
2300     errorMessage = null;
2301     uniqueSetSuffix = null;
2302     seqRefIds = null;
2303     viewportsAdded.clear();
2304     frefedSequence = null;
2305
2306     if (file.startsWith("http://"))
2307     {
2308       url = new URL(file);
2309     }
2310     final URL _url = url;
2311     return new jarInputStreamProvider()
2312     {
2313
2314       @Override
2315       public JarInputStream getJarInputStream() throws IOException
2316       {
2317         if (_url != null)
2318         {
2319           return new JarInputStream(_url.openStream());
2320         }
2321         else
2322         {
2323           return new JarInputStream(new FileInputStream(file));
2324         }
2325       }
2326
2327       @Override
2328       public String getFilename()
2329       {
2330         return file;
2331       }
2332     };
2333   }
2334
2335   /**
2336    * Recover jalview session from a jalview project archive. Caller may
2337    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2338    * themselves. Any null fields will be initialised with default values,
2339    * non-null fields are left alone.
2340    * 
2341    * @param jprovider
2342    * @return
2343    */
2344   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2345   {
2346     errorMessage = null;
2347     if (uniqueSetSuffix == null)
2348     {
2349       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2350     }
2351     if (seqRefIds == null)
2352     {
2353       initSeqRefs();
2354     }
2355     AlignFrame af = null, _af = null;
2356     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
2357     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
2358     final String file = jprovider.getFilename();
2359     try
2360     {
2361       JarInputStream jin = null;
2362       JarEntry jarentry = null;
2363       int entryCount = 1;
2364
2365       do
2366       {
2367         jin = jprovider.getJarInputStream();
2368         for (int i = 0; i < entryCount; i++)
2369         {
2370           jarentry = jin.getNextJarEntry();
2371         }
2372
2373         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2374         {
2375           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2376           JalviewModel object = new JalviewModel();
2377
2378           Unmarshaller unmar = new Unmarshaller(object);
2379           unmar.setValidation(false);
2380           object = (JalviewModel) unmar.unmarshal(in);
2381           if (true) // !skipViewport(object))
2382           {
2383             _af = loadFromObject(object, file, true, jprovider);
2384             if (_af != null && object.getJalviewModelSequence()
2385                     .getViewportCount() > 0)
2386             {
2387               if (af == null)
2388               {
2389                 // store a reference to the first view
2390                 af = _af;
2391               }
2392               if (_af.viewport.isGatherViewsHere())
2393               {
2394                 // if this is a gathered view, keep its reference since
2395                 // after gathering views, only this frame will remain
2396                 af = _af;
2397                 gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af);
2398               }
2399               // Save dataset to register mappings once all resolved
2400               importedDatasets.put(af.viewport.getAlignment().getDataset(),
2401                       af.viewport.getAlignment().getDataset());
2402             }
2403           }
2404           entryCount++;
2405         }
2406         else if (jarentry != null)
2407         {
2408           // Some other file here.
2409           entryCount++;
2410         }
2411       } while (jarentry != null);
2412       resolveFrefedSequences();
2413     } catch (IOException ex)
2414     {
2415       ex.printStackTrace();
2416       errorMessage = "Couldn't locate Jalview XML file : " + file;
2417       System.err.println(
2418               "Exception whilst loading jalview XML file : " + ex + "\n");
2419     } catch (Exception ex)
2420     {
2421       System.err.println("Parsing as Jalview Version 2 file failed.");
2422       ex.printStackTrace(System.err);
2423       if (attemptversion1parse)
2424       {
2425         // Is Version 1 Jar file?
2426         try
2427         {
2428           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2429         } catch (Exception ex2)
2430         {
2431           System.err.println("Exception whilst loading as jalviewXMLV1:");
2432           ex2.printStackTrace();
2433           af = null;
2434         }
2435       }
2436       if (Desktop.instance != null)
2437       {
2438         Desktop.instance.stopLoading();
2439       }
2440       if (af != null)
2441       {
2442         System.out.println("Successfully loaded archive file");
2443         return af;
2444       }
2445       ex.printStackTrace();
2446
2447       System.err.println(
2448               "Exception whilst loading jalview XML file : " + ex + "\n");
2449     } catch (OutOfMemoryError e)
2450     {
2451       // Don't use the OOM Window here
2452       errorMessage = "Out of memory loading jalview XML file";
2453       System.err.println("Out of memory whilst loading jalview XML file");
2454       e.printStackTrace();
2455     }
2456
2457     /*
2458      * Regather multiple views (with the same sequence set id) to the frame (if
2459      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2460      * views instead of separate frames. Note this doesn't restore a state where
2461      * some expanded views in turn have tabbed views - the last "first tab" read
2462      * in will play the role of gatherer for all.
2463      */
2464     for (AlignFrame fr : gatherToThisFrame.values())
2465     {
2466       Desktop.instance.gatherViews(fr);
2467     }
2468
2469     restoreSplitFrames();
2470     for (AlignmentI ds : importedDatasets.keySet())
2471     {
2472       if (ds.getCodonFrames() != null)
2473       {
2474         StructureSelectionManager
2475                 .getStructureSelectionManager(Desktop.instance)
2476                 .registerMappings(ds.getCodonFrames());
2477       }
2478     }
2479     if (errorMessage != null)
2480     {
2481       reportErrors();
2482     }
2483
2484     if (Desktop.instance != null)
2485     {
2486       Desktop.instance.stopLoading();
2487     }
2488
2489     return af;
2490   }
2491
2492   /**
2493    * Try to reconstruct and display SplitFrame windows, where each contains
2494    * complementary dna and protein alignments. Done by pairing up AlignFrame
2495    * objects (created earlier) which have complementary viewport ids associated.
2496    */
2497   protected void restoreSplitFrames()
2498   {
2499     List<SplitFrame> gatherTo = new ArrayList<>();
2500     List<AlignFrame> addedToSplitFrames = new ArrayList<>();
2501     Map<String, AlignFrame> dna = new HashMap<>();
2502
2503     /*
2504      * Identify the DNA alignments
2505      */
2506     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2507             .entrySet())
2508     {
2509       AlignFrame af = candidate.getValue();
2510       if (af.getViewport().getAlignment().isNucleotide())
2511       {
2512         dna.put(candidate.getKey().getId(), af);
2513       }
2514     }
2515
2516     /*
2517      * Try to match up the protein complements
2518      */
2519     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2520             .entrySet())
2521     {
2522       AlignFrame af = candidate.getValue();
2523       if (!af.getViewport().getAlignment().isNucleotide())
2524       {
2525         String complementId = candidate.getKey().getComplementId();
2526         // only non-null complements should be in the Map
2527         if (complementId != null && dna.containsKey(complementId))
2528         {
2529           final AlignFrame dnaFrame = dna.get(complementId);
2530           SplitFrame sf = createSplitFrame(dnaFrame, af);
2531           addedToSplitFrames.add(dnaFrame);
2532           addedToSplitFrames.add(af);
2533           dnaFrame.setMenusForViewport();
2534           af.setMenusForViewport();
2535           if (af.viewport.isGatherViewsHere())
2536           {
2537             gatherTo.add(sf);
2538           }
2539         }
2540       }
2541     }
2542
2543     /*
2544      * Open any that we failed to pair up (which shouldn't happen!) as
2545      * standalone AlignFrame's.
2546      */
2547     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2548             .entrySet())
2549     {
2550       AlignFrame af = candidate.getValue();
2551       if (!addedToSplitFrames.contains(af))
2552       {
2553         Viewport view = candidate.getKey();
2554         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2555                 view.getHeight());
2556         af.setMenusForViewport();
2557         System.err.println("Failed to restore view " + view.getTitle()
2558                 + " to split frame");
2559       }
2560     }
2561
2562     /*
2563      * Gather back into tabbed views as flagged.
2564      */
2565     for (SplitFrame sf : gatherTo)
2566     {
2567       Desktop.instance.gatherViews(sf);
2568     }
2569
2570     splitFrameCandidates.clear();
2571   }
2572
2573   /**
2574    * Construct and display one SplitFrame holding DNA and protein alignments.
2575    * 
2576    * @param dnaFrame
2577    * @param proteinFrame
2578    * @return
2579    */
2580   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2581           AlignFrame proteinFrame)
2582   {
2583     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2584     String title = MessageManager.getString("label.linked_view_title");
2585     int width = (int) dnaFrame.getBounds().getWidth();
2586     int height = (int) (dnaFrame.getBounds().getHeight()
2587             + proteinFrame.getBounds().getHeight() + 50);
2588
2589     /*
2590      * SplitFrame location is saved to both enclosed frames
2591      */
2592     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
2593     Desktop.addInternalFrame(splitFrame, title, width, height);
2594
2595     /*
2596      * And compute cDNA consensus (couldn't do earlier with consensus as
2597      * mappings were not yet present)
2598      */
2599     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2600
2601     return splitFrame;
2602   }
2603
2604   /**
2605    * check errorMessage for a valid error message and raise an error box in the
2606    * GUI or write the current errorMessage to stderr and then clear the error
2607    * state.
2608    */
2609   protected void reportErrors()
2610   {
2611     reportErrors(false);
2612   }
2613
2614   protected void reportErrors(final boolean saving)
2615   {
2616     if (errorMessage != null)
2617     {
2618       final String finalErrorMessage = errorMessage;
2619       if (raiseGUI)
2620       {
2621         javax.swing.SwingUtilities.invokeLater(new Runnable()
2622         {
2623           @Override
2624           public void run()
2625           {
2626             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
2627                     finalErrorMessage,
2628                     "Error " + (saving ? "saving" : "loading")
2629                             + " Jalview file",
2630                     JvOptionPane.WARNING_MESSAGE);
2631           }
2632         });
2633       }
2634       else
2635       {
2636         System.err.println("Problem loading Jalview file: " + errorMessage);
2637       }
2638     }
2639     errorMessage = null;
2640   }
2641
2642   Map<String, String> alreadyLoadedPDB = new HashMap<>();
2643
2644   /**
2645    * when set, local views will be updated from view stored in JalviewXML
2646    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2647    * sync if this is set to true.
2648    */
2649   private final boolean updateLocalViews = false;
2650
2651   /**
2652    * Returns the path to a temporary file holding the PDB file for the given PDB
2653    * id. The first time of asking, searches for a file of that name in the
2654    * Jalview project jar, and copies it to a new temporary file. Any repeat
2655    * requests just return the path to the file previously created.
2656    * 
2657    * @param jprovider
2658    * @param pdbId
2659    * @return
2660    */
2661   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
2662           String origFile)
2663   {
2664     if (alreadyLoadedPDB.containsKey(pdbId))
2665     {
2666       return alreadyLoadedPDB.get(pdbId).toString();
2667     }
2668
2669     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
2670             origFile);
2671     if (tempFile != null)
2672     {
2673       alreadyLoadedPDB.put(pdbId, tempFile);
2674     }
2675     return tempFile;
2676   }
2677
2678   /**
2679    * Copies the jar entry of given name to a new temporary file and returns the
2680    * path to the file, or null if the entry is not found.
2681    * 
2682    * @param jprovider
2683    * @param jarEntryName
2684    * @param prefix
2685    *          a prefix for the temporary file name, must be at least three
2686    *          characters long
2687    * @param origFile
2688    *          null or original file - so new file can be given the same suffix
2689    *          as the old one
2690    * @return
2691    */
2692   protected String copyJarEntry(jarInputStreamProvider jprovider,
2693           String jarEntryName, String prefix, String origFile)
2694   {
2695     BufferedReader in = null;
2696     PrintWriter out = null;
2697     String suffix = ".tmp";
2698     if (origFile == null)
2699     {
2700       origFile = jarEntryName;
2701     }
2702     int sfpos = origFile.lastIndexOf(".");
2703     if (sfpos > -1 && sfpos < (origFile.length() - 3))
2704     {
2705       suffix = "." + origFile.substring(sfpos + 1);
2706     }
2707     try
2708     {
2709       JarInputStream jin = jprovider.getJarInputStream();
2710       /*
2711        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2712        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2713        * FileInputStream(jprovider)); }
2714        */
2715
2716       JarEntry entry = null;
2717       do
2718       {
2719         entry = jin.getNextJarEntry();
2720       } while (entry != null && !entry.getName().equals(jarEntryName));
2721       if (entry != null)
2722       {
2723         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2724         File outFile = File.createTempFile(prefix, suffix);
2725         outFile.deleteOnExit();
2726         out = new PrintWriter(new FileOutputStream(outFile));
2727         String data;
2728
2729         while ((data = in.readLine()) != null)
2730         {
2731           out.println(data);
2732         }
2733         out.flush();
2734         String t = outFile.getAbsolutePath();
2735         return t;
2736       }
2737       else
2738       {
2739         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2740       }
2741     } catch (Exception ex)
2742     {
2743       ex.printStackTrace();
2744     } finally
2745     {
2746       if (in != null)
2747       {
2748         try
2749         {
2750           in.close();
2751         } catch (IOException e)
2752         {
2753           // ignore
2754         }
2755       }
2756       if (out != null)
2757       {
2758         out.close();
2759       }
2760     }
2761
2762     return null;
2763   }
2764
2765   private class JvAnnotRow
2766   {
2767     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2768     {
2769       order = i;
2770       template = jaa;
2771     }
2772
2773     /**
2774      * persisted version of annotation row from which to take vis properties
2775      */
2776     public jalview.datamodel.AlignmentAnnotation template;
2777
2778     /**
2779      * original position of the annotation row in the alignment
2780      */
2781     public int order;
2782   }
2783
2784   /**
2785    * Load alignment frame from jalview XML DOM object
2786    * 
2787    * @param object
2788    *          DOM
2789    * @param file
2790    *          filename source string
2791    * @param loadTreesAndStructures
2792    *          when false only create Viewport
2793    * @param jprovider
2794    *          data source provider
2795    * @return alignment frame created from view stored in DOM
2796    */
2797   AlignFrame loadFromObject(JalviewModel object, String file,
2798           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2799   {
2800     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2801     Sequence[] vamsasSeq = vamsasSet.getSequence();
2802
2803     JalviewModelSequence jms = object.getJalviewModelSequence();
2804
2805     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2806             : null;
2807
2808     // ////////////////////////////////
2809     // LOAD SEQUENCES
2810
2811     List<SequenceI> hiddenSeqs = null;
2812
2813     List<SequenceI> tmpseqs = new ArrayList<>();
2814
2815     boolean multipleView = false;
2816     SequenceI referenceseqForView = null;
2817     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2818     int vi = 0; // counter in vamsasSeq array
2819     for (int i = 0; i < jseqs.length; i++)
2820     {
2821       String seqId = jseqs[i].getId();
2822
2823       SequenceI tmpSeq = seqRefIds.get(seqId);
2824       if (tmpSeq != null)
2825       {
2826         if (!incompleteSeqs.containsKey(seqId))
2827         {
2828           // may not need this check, but keep it for at least 2.9,1 release
2829           if (tmpSeq.getStart() != jseqs[i].getStart()
2830                   || tmpSeq.getEnd() != jseqs[i].getEnd())
2831           {
2832             System.err.println(
2833                     "Warning JAL-2154 regression: updating start/end for sequence "
2834                             + tmpSeq.toString() + " to " + jseqs[i]);
2835           }
2836         }
2837         else
2838         {
2839           incompleteSeqs.remove(seqId);
2840         }
2841         if (vamsasSeq.length > vi && vamsasSeq[vi].getId().equals(seqId))
2842         {
2843           // most likely we are reading a dataset XML document so
2844           // update from vamsasSeq section of XML for this sequence
2845           tmpSeq.setName(vamsasSeq[vi].getName());
2846           tmpSeq.setDescription(vamsasSeq[vi].getDescription());
2847           tmpSeq.setSequence(vamsasSeq[vi].getSequence());
2848           vi++;
2849         }
2850         else
2851         {
2852           // reading multiple views, so vamsasSeq set is a subset of JSeq
2853           multipleView = true;
2854         }
2855         tmpSeq.setStart(jseqs[i].getStart());
2856         tmpSeq.setEnd(jseqs[i].getEnd());
2857         tmpseqs.add(tmpSeq);
2858       }
2859       else
2860       {
2861         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2862                 vamsasSeq[vi].getSequence());
2863         tmpSeq.setDescription(vamsasSeq[vi].getDescription());
2864         tmpSeq.setStart(jseqs[i].getStart());
2865         tmpSeq.setEnd(jseqs[i].getEnd());
2866         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
2867         seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq);
2868         tmpseqs.add(tmpSeq);
2869         vi++;
2870       }
2871
2872       if (jseqs[i].hasViewreference() && jseqs[i].getViewreference())
2873       {
2874         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
2875       }
2876
2877       if (jseqs[i].getHidden())
2878       {
2879         if (hiddenSeqs == null)
2880         {
2881           hiddenSeqs = new ArrayList<>();
2882         }
2883
2884         hiddenSeqs.add(tmpSeq);
2885       }
2886     }
2887
2888     // /
2889     // Create the alignment object from the sequence set
2890     // ///////////////////////////////
2891     SequenceI[] orderedSeqs = tmpseqs
2892             .toArray(new SequenceI[tmpseqs.size()]);
2893
2894     AlignmentI al = null;
2895     // so we must create or recover the dataset alignment before going further
2896     // ///////////////////////////////
2897     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2898     {
2899       // older jalview projects do not have a dataset - so creat alignment and
2900       // dataset
2901       al = new Alignment(orderedSeqs);
2902       al.setDataset(null);
2903     }
2904     else
2905     {
2906       boolean isdsal = object.getJalviewModelSequence()
2907               .getViewportCount() == 0;
2908       if (isdsal)
2909       {
2910         // we are importing a dataset record, so
2911         // recover reference to an alignment already materialsed as dataset
2912         al = getDatasetFor(vamsasSet.getDatasetId());
2913       }
2914       if (al == null)
2915       {
2916         // materialse the alignment
2917         al = new Alignment(orderedSeqs);
2918       }
2919       if (isdsal)
2920       {
2921         addDatasetRef(vamsasSet.getDatasetId(), al);
2922       }
2923
2924       // finally, verify all data in vamsasSet is actually present in al
2925       // passing on flag indicating if it is actually a stored dataset
2926       recoverDatasetFor(vamsasSet, al, isdsal);
2927     }
2928
2929     if (referenceseqForView != null)
2930     {
2931       al.setSeqrep(referenceseqForView);
2932     }
2933     // / Add the alignment properties
2934     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2935     {
2936       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2937       al.setProperty(ssp.getKey(), ssp.getValue());
2938     }
2939
2940     // ///////////////////////////////
2941
2942     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2943     if (!multipleView)
2944     {
2945       // load sequence features, database references and any associated PDB
2946       // structures for the alignment
2947       //
2948       // prior to 2.10, this part would only be executed the first time a
2949       // sequence was encountered, but not afterwards.
2950       // now, for 2.10 projects, this is also done if the xml doc includes
2951       // dataset sequences not actually present in any particular view.
2952       //
2953       for (int i = 0; i < vamsasSeq.length; i++)
2954       {
2955         if (jseqs[i].getFeaturesCount() > 0)
2956         {
2957           Features[] features = jseqs[i].getFeatures();
2958           for (int f = 0; f < features.length; f++)
2959           {
2960             SequenceFeature sf = new SequenceFeature(features[f].getType(),
2961                     features[f].getDescription(), features[f].getBegin(),
2962                     features[f].getEnd(), features[f].getScore(),
2963                     features[f].getFeatureGroup());
2964             sf.setStatus(features[f].getStatus());
2965             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2966             {
2967               OtherData keyValue = features[f].getOtherData(od);
2968               if (keyValue.getKey().startsWith("LINK"))
2969               {
2970                 sf.addLink(keyValue.getValue());
2971               }
2972               else
2973               {
2974                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2975               }
2976
2977             }
2978             // adds feature to datasequence's feature set (since Jalview 2.10)
2979             al.getSequenceAt(i).addSequenceFeature(sf);
2980           }
2981         }
2982         if (vamsasSeq[i].getDBRefCount() > 0)
2983         {
2984           // adds dbrefs to datasequence's set (since Jalview 2.10)
2985           addDBRefs(
2986                   al.getSequenceAt(i).getDatasetSequence() == null
2987                           ? al.getSequenceAt(i)
2988                           : al.getSequenceAt(i).getDatasetSequence(),
2989                   vamsasSeq[i]);
2990         }
2991         if (jseqs[i].getPdbidsCount() > 0)
2992         {
2993           Pdbids[] ids = jseqs[i].getPdbids();
2994           for (int p = 0; p < ids.length; p++)
2995           {
2996             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2997             entry.setId(ids[p].getId());
2998             if (ids[p].getType() != null)
2999             {
3000               if (PDBEntry.Type.getType(ids[p].getType()) != null)
3001               {
3002                 entry.setType(PDBEntry.Type.getType(ids[p].getType()));
3003               }
3004               else
3005               {
3006                 entry.setType(PDBEntry.Type.FILE);
3007               }
3008             }
3009             // jprovider is null when executing 'New View'
3010             if (ids[p].getFile() != null && jprovider != null)
3011             {
3012               if (!pdbloaded.containsKey(ids[p].getFile()))
3013               {
3014                 entry.setFile(loadPDBFile(jprovider, ids[p].getId(),
3015                         ids[p].getFile()));
3016               }
3017               else
3018               {
3019                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
3020               }
3021             }
3022             if (ids[p].getPdbentryItem() != null)
3023             {
3024               for (PdbentryItem item : ids[p].getPdbentryItem())
3025               {
3026                 for (Property pr : item.getProperty())
3027                 {
3028                   entry.setProperty(pr.getName(), pr.getValue());
3029                 }
3030               }
3031             }
3032             StructureSelectionManager
3033                     .getStructureSelectionManager(Desktop.instance)
3034                     .registerPDBEntry(entry);
3035             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3036             if (al.getSequenceAt(i).getDatasetSequence() != null)
3037             {
3038               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3039             }
3040             else
3041             {
3042               al.getSequenceAt(i).addPDBId(entry);
3043             }
3044           }
3045         }
3046       }
3047     } // end !multipleview
3048
3049     // ///////////////////////////////
3050     // LOAD SEQUENCE MAPPINGS
3051
3052     if (vamsasSet.getAlcodonFrameCount() > 0)
3053     {
3054       // TODO Potentially this should only be done once for all views of an
3055       // alignment
3056       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
3057       for (int i = 0; i < alc.length; i++)
3058       {
3059         AlignedCodonFrame cf = new AlignedCodonFrame();
3060         if (alc[i].getAlcodMapCount() > 0)
3061         {
3062           AlcodMap[] maps = alc[i].getAlcodMap();
3063           for (int m = 0; m < maps.length; m++)
3064           {
3065             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
3066             // Load Mapping
3067             jalview.datamodel.Mapping mapping = null;
3068             // attach to dna sequence reference.
3069             if (maps[m].getMapping() != null)
3070             {
3071               mapping = addMapping(maps[m].getMapping());
3072               if (dnaseq != null && mapping.getTo() != null)
3073               {
3074                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3075               }
3076               else
3077               {
3078                 // defer to later
3079                 frefedSequence.add(
3080                         newAlcodMapRef(maps[m].getDnasq(), cf, mapping));
3081               }
3082             }
3083           }
3084           al.addCodonFrame(cf);
3085         }
3086       }
3087     }
3088
3089     // ////////////////////////////////
3090     // LOAD ANNOTATIONS
3091     List<JvAnnotRow> autoAlan = new ArrayList<>();
3092
3093     /*
3094      * store any annotations which forward reference a group's ID
3095      */
3096     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
3097
3098     if (vamsasSet.getAnnotationCount() > 0)
3099     {
3100       Annotation[] an = vamsasSet.getAnnotation();
3101
3102       for (int i = 0; i < an.length; i++)
3103       {
3104         Annotation annotation = an[i];
3105
3106         /**
3107          * test if annotation is automatically calculated for this view only
3108          */
3109         boolean autoForView = false;
3110         if (annotation.getLabel().equals("Quality")
3111                 || annotation.getLabel().equals("Conservation")
3112                 || annotation.getLabel().equals("Consensus"))
3113         {
3114           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3115           autoForView = true;
3116           if (!annotation.hasAutoCalculated())
3117           {
3118             annotation.setAutoCalculated(true);
3119           }
3120         }
3121         if (autoForView || (annotation.hasAutoCalculated()
3122                 && annotation.isAutoCalculated()))
3123         {
3124           // remove ID - we don't recover annotation from other views for
3125           // view-specific annotation
3126           annotation.setId(null);
3127         }
3128
3129         // set visiblity for other annotation in this view
3130         String annotationId = annotation.getId();
3131         if (annotationId != null && annotationIds.containsKey(annotationId))
3132         {
3133           AlignmentAnnotation jda = annotationIds.get(annotationId);
3134           // in principle Visible should always be true for annotation displayed
3135           // in multiple views
3136           if (annotation.hasVisible())
3137           {
3138             jda.visible = annotation.getVisible();
3139           }
3140
3141           al.addAnnotation(jda);
3142
3143           continue;
3144         }
3145         // Construct new annotation from model.
3146         AnnotationElement[] ae = annotation.getAnnotationElement();
3147         jalview.datamodel.Annotation[] anot = null;
3148         java.awt.Color firstColour = null;
3149         int anpos;
3150         if (!annotation.getScoreOnly())
3151         {
3152           anot = new jalview.datamodel.Annotation[al.getWidth()];
3153           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
3154           {
3155             anpos = ae[aa].getPosition();
3156
3157             if (anpos >= anot.length)
3158             {
3159               continue;
3160             }
3161
3162             anot[anpos] = new jalview.datamodel.Annotation(
3163
3164                     ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
3165                     (ae[aa].getSecondaryStructure() == null
3166                             || ae[aa].getSecondaryStructure().length() == 0)
3167                                     ? ' '
3168                                     : ae[aa].getSecondaryStructure()
3169                                             .charAt(0),
3170                     ae[aa].getValue()
3171
3172             );
3173             // JBPNote: Consider verifying dataflow for IO of secondary
3174             // structure annotation read from Stockholm files
3175             // this was added to try to ensure that
3176             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
3177             // {
3178             // anot[ae[aa].getPosition()].displayCharacter = "";
3179             // }
3180             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
3181             if (firstColour == null)
3182             {
3183               firstColour = anot[anpos].colour;
3184             }
3185           }
3186         }
3187         jalview.datamodel.AlignmentAnnotation jaa = null;
3188
3189         if (annotation.getGraph())
3190         {
3191           float llim = 0, hlim = 0;
3192           // if (autoForView || an[i].isAutoCalculated()) {
3193           // hlim=11f;
3194           // }
3195           jaa = new jalview.datamodel.AlignmentAnnotation(
3196                   annotation.getLabel(), annotation.getDescription(), anot,
3197                   llim, hlim, annotation.getGraphType());
3198
3199           jaa.graphGroup = annotation.getGraphGroup();
3200           jaa._linecolour = firstColour;
3201           if (annotation.getThresholdLine() != null)
3202           {
3203             jaa.setThreshold(new jalview.datamodel.GraphLine(
3204                     annotation.getThresholdLine().getValue(),
3205                     annotation.getThresholdLine().getLabel(),
3206                     new java.awt.Color(
3207                             annotation.getThresholdLine().getColour())));
3208
3209           }
3210           if (autoForView || annotation.isAutoCalculated())
3211           {
3212             // Hardwire the symbol display line to ensure that labels for
3213             // histograms are displayed
3214             jaa.hasText = true;
3215           }
3216         }
3217         else
3218         {
3219           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
3220                   an[i].getDescription(), anot);
3221           jaa._linecolour = firstColour;
3222         }
3223         // register new annotation
3224         if (an[i].getId() != null)
3225         {
3226           annotationIds.put(an[i].getId(), jaa);
3227           jaa.annotationId = an[i].getId();
3228         }
3229         // recover sequence association
3230         String sequenceRef = an[i].getSequenceRef();
3231         if (sequenceRef != null)
3232         {
3233           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3234           SequenceI sequence = seqRefIds.get(sequenceRef);
3235           if (sequence == null)
3236           {
3237             // in pre-2.9 projects sequence ref is to sequence name
3238             sequence = al.findName(sequenceRef);
3239           }
3240           if (sequence != null)
3241           {
3242             jaa.createSequenceMapping(sequence, 1, true);
3243             sequence.addAlignmentAnnotation(jaa);
3244           }
3245         }
3246         // and make a note of any group association
3247         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3248         {
3249           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3250                   .get(an[i].getGroupRef());
3251           if (aal == null)
3252           {
3253             aal = new ArrayList<>();
3254             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3255           }
3256           aal.add(jaa);
3257         }
3258
3259         if (an[i].hasScore())
3260         {
3261           jaa.setScore(an[i].getScore());
3262         }
3263         if (an[i].hasVisible())
3264         {
3265           jaa.visible = an[i].getVisible();
3266         }
3267
3268         if (an[i].hasCentreColLabels())
3269         {
3270           jaa.centreColLabels = an[i].getCentreColLabels();
3271         }
3272
3273         if (an[i].hasScaleColLabels())
3274         {
3275           jaa.scaleColLabel = an[i].getScaleColLabels();
3276         }
3277         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3278         {
3279           // newer files have an 'autoCalculated' flag and store calculation
3280           // state in viewport properties
3281           jaa.autoCalculated = true; // means annotation will be marked for
3282           // update at end of load.
3283         }
3284         if (an[i].hasGraphHeight())
3285         {
3286           jaa.graphHeight = an[i].getGraphHeight();
3287         }
3288         if (an[i].hasBelowAlignment())
3289         {
3290           jaa.belowAlignment = an[i].isBelowAlignment();
3291         }
3292         jaa.setCalcId(an[i].getCalcId());
3293         if (an[i].getPropertyCount() > 0)
3294         {
3295           for (jalview.schemabinding.version2.Property prop : an[i]
3296                   .getProperty())
3297           {
3298             jaa.setProperty(prop.getName(), prop.getValue());
3299           }
3300         }
3301         if (jaa.autoCalculated)
3302         {
3303           autoAlan.add(new JvAnnotRow(i, jaa));
3304         }
3305         else
3306         // if (!autoForView)
3307         {
3308           // add autocalculated group annotation and any user created annotation
3309           // for the view
3310           al.addAnnotation(jaa);
3311         }
3312       }
3313     }
3314     // ///////////////////////
3315     // LOAD GROUPS
3316     // Create alignment markup and styles for this view
3317     if (jms.getJGroupCount() > 0)
3318     {
3319       JGroup[] groups = jms.getJGroup();
3320       boolean addAnnotSchemeGroup = false;
3321       for (int i = 0; i < groups.length; i++)
3322       {
3323         JGroup jGroup = groups[i];
3324         ColourSchemeI cs = null;
3325         if (jGroup.getColour() != null)
3326         {
3327           if (jGroup.getColour().startsWith("ucs"))
3328           {
3329             cs = getUserColourScheme(jms, jGroup.getColour());
3330           }
3331           else if (jGroup.getColour().equals("AnnotationColourGradient")
3332                   && jGroup.getAnnotationColours() != null)
3333           {
3334             addAnnotSchemeGroup = true;
3335           }
3336           else
3337           {
3338             cs = ColourSchemeProperty.getColourScheme(al,
3339                     jGroup.getColour());
3340           }
3341         }
3342         int pidThreshold = jGroup.getPidThreshold();
3343
3344         Vector<SequenceI> seqs = new Vector<>();
3345
3346         for (int s = 0; s < jGroup.getSeqCount(); s++)
3347         {
3348           String seqId = jGroup.getSeq(s) + "";
3349           SequenceI ts = seqRefIds.get(seqId);
3350
3351           if (ts != null)
3352           {
3353             seqs.addElement(ts);
3354           }
3355         }
3356
3357         if (seqs.size() < 1)
3358         {
3359           continue;
3360         }
3361
3362         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3363                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3364                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3365         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
3366         sg.getGroupColourScheme()
3367                 .setConservationInc(jGroup.getConsThreshold());
3368         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3369
3370         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3371         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3372         sg.setShowNonconserved(
3373                 jGroup.hasShowUnconserved() ? jGroup.isShowUnconserved()
3374                         : false);
3375         sg.thresholdTextColour = jGroup.getTextColThreshold();
3376         if (jGroup.hasShowConsensusHistogram())
3377         {
3378           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3379         }
3380         ;
3381         if (jGroup.hasShowSequenceLogo())
3382         {
3383           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3384         }
3385         if (jGroup.hasNormaliseSequenceLogo())
3386         {
3387           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3388         }
3389         if (jGroup.hasIgnoreGapsinConsensus())
3390         {
3391           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3392         }
3393         if (jGroup.getConsThreshold() != 0)
3394         {
3395           Conservation c = new Conservation("All", sg.getSequences(null), 0,
3396                   sg.getWidth() - 1);
3397           c.calculate();
3398           c.verdict(false, 25);
3399           sg.cs.setConservation(c);
3400         }
3401
3402         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3403         {
3404           // re-instate unique group/annotation row reference
3405           List<AlignmentAnnotation> jaal = groupAnnotRefs
3406                   .get(jGroup.getId());
3407           if (jaal != null)
3408           {
3409             for (AlignmentAnnotation jaa : jaal)
3410             {
3411               jaa.groupRef = sg;
3412               if (jaa.autoCalculated)
3413               {
3414                 // match up and try to set group autocalc alignment row for this
3415                 // annotation
3416                 if (jaa.label.startsWith("Consensus for "))
3417                 {
3418                   sg.setConsensus(jaa);
3419                 }
3420                 // match up and try to set group autocalc alignment row for this
3421                 // annotation
3422                 if (jaa.label.startsWith("Conservation for "))
3423                 {
3424                   sg.setConservationRow(jaa);
3425                 }
3426               }
3427             }
3428           }
3429         }
3430         al.addGroup(sg);
3431         if (addAnnotSchemeGroup)
3432         {
3433           // reconstruct the annotation colourscheme
3434           sg.setColourScheme(constructAnnotationColour(
3435                   jGroup.getAnnotationColours(), null, al, jms, false));
3436         }
3437       }
3438     }
3439     if (view == null)
3440     {
3441       // only dataset in this model, so just return.
3442       return null;
3443     }
3444     // ///////////////////////////////
3445     // LOAD VIEWPORT
3446
3447     // If we just load in the same jar file again, the sequenceSetId
3448     // will be the same, and we end up with multiple references
3449     // to the same sequenceSet. We must modify this id on load
3450     // so that each load of the file gives a unique id
3451     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3452     String viewId = (view.getId() == null ? null
3453             : view.getId() + uniqueSetSuffix);
3454     AlignFrame af = null;
3455     AlignViewport av = null;
3456     // now check to see if we really need to create a new viewport.
3457     if (multipleView && viewportsAdded.size() == 0)
3458     {
3459       // We recovered an alignment for which a viewport already exists.
3460       // TODO: fix up any settings necessary for overlaying stored state onto
3461       // state recovered from another document. (may not be necessary).
3462       // we may need a binding from a viewport in memory to one recovered from
3463       // XML.
3464       // and then recover its containing af to allow the settings to be applied.
3465       // TODO: fix for vamsas demo
3466       System.err.println(
3467               "About to recover a viewport for existing alignment: Sequence set ID is "
3468                       + uniqueSeqSetId);
3469       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3470       if (seqsetobj != null)
3471       {
3472         if (seqsetobj instanceof String)
3473         {
3474           uniqueSeqSetId = (String) seqsetobj;
3475           System.err.println(
3476                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3477                           + uniqueSeqSetId);
3478         }
3479         else
3480         {
3481           System.err.println(
3482                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
3483         }
3484
3485       }
3486     }
3487     /**
3488      * indicate that annotation colours are applied across all groups (pre
3489      * Jalview 2.8.1 behaviour)
3490      */
3491     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
3492             object.getVersion());
3493
3494     AlignmentPanel ap = null;
3495     boolean isnewview = true;
3496     if (viewId != null)
3497     {
3498       // Check to see if this alignment already has a view id == viewId
3499       jalview.gui.AlignmentPanel views[] = Desktop
3500               .getAlignmentPanels(uniqueSeqSetId);
3501       if (views != null && views.length > 0)
3502       {
3503         for (int v = 0; v < views.length; v++)
3504         {
3505           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3506           {
3507             // recover the existing alignpanel, alignframe, viewport
3508             af = views[v].alignFrame;
3509             av = views[v].av;
3510             ap = views[v];
3511             // TODO: could even skip resetting view settings if we don't want to
3512             // change the local settings from other jalview processes
3513             isnewview = false;
3514           }
3515         }
3516       }
3517     }
3518
3519     if (isnewview)
3520     {
3521       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3522               uniqueSeqSetId, viewId, autoAlan);
3523       av = af.viewport;
3524       ap = af.alignPanel;
3525     }
3526
3527     /*
3528      * Load any trees, PDB structures and viewers
3529      * 
3530      * Not done if flag is false (when this method is used for New View)
3531      */
3532     if (loadTreesAndStructures)
3533     {
3534       loadTrees(jms, view, af, av, ap);
3535       loadPDBStructures(jprovider, jseqs, af, ap);
3536       loadRnaViewers(jprovider, jseqs, ap);
3537     }
3538     // and finally return.
3539     return af;
3540   }
3541
3542   /**
3543    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3544    * panel is restored from separate jar entries, two (gapped and trimmed) per
3545    * sequence and secondary structure.
3546    * 
3547    * Currently each viewer shows just one sequence and structure (gapped and
3548    * trimmed), however this method is designed to support multiple sequences or
3549    * structures in viewers if wanted in future.
3550    * 
3551    * @param jprovider
3552    * @param jseqs
3553    * @param ap
3554    */
3555   private void loadRnaViewers(jarInputStreamProvider jprovider,
3556           JSeq[] jseqs, AlignmentPanel ap)
3557   {
3558     /*
3559      * scan the sequences for references to viewers; create each one the first
3560      * time it is referenced, add Rna models to existing viewers
3561      */
3562     for (JSeq jseq : jseqs)
3563     {
3564       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3565       {
3566         RnaViewer viewer = jseq.getRnaViewer(i);
3567         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
3568                 ap);
3569
3570         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3571         {
3572           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3573           SequenceI seq = seqRefIds.get(jseq.getId());
3574           AlignmentAnnotation ann = this.annotationIds
3575                   .get(ss.getAnnotationId());
3576
3577           /*
3578            * add the structure to the Varna display (with session state copied
3579            * from the jar to a temporary file)
3580            */
3581           boolean gapped = ss.isGapped();
3582           String rnaTitle = ss.getTitle();
3583           String sessionState = ss.getViewerState();
3584           String tempStateFile = copyJarEntry(jprovider, sessionState,
3585                   "varna", null);
3586           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3587           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3588         }
3589         appVarna.setInitialSelection(viewer.getSelectedRna());
3590       }
3591     }
3592   }
3593
3594   /**
3595    * Locate and return an already instantiated matching AppVarna, or create one
3596    * if not found
3597    * 
3598    * @param viewer
3599    * @param viewIdSuffix
3600    * @param ap
3601    * @return
3602    */
3603   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3604           String viewIdSuffix, AlignmentPanel ap)
3605   {
3606     /*
3607      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3608      * if load is repeated
3609      */
3610     String postLoadId = viewer.getViewId() + viewIdSuffix;
3611     for (JInternalFrame frame : getAllFrames())
3612     {
3613       if (frame instanceof AppVarna)
3614       {
3615         AppVarna varna = (AppVarna) frame;
3616         if (postLoadId.equals(varna.getViewId()))
3617         {
3618           // this viewer is already instantiated
3619           // could in future here add ap as another 'parent' of the
3620           // AppVarna window; currently just 1-to-many
3621           return varna;
3622         }
3623       }
3624     }
3625
3626     /*
3627      * viewer not found - make it
3628      */
3629     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
3630             viewer.getXpos(), viewer.getYpos(), viewer.getWidth(),
3631             viewer.getHeight(), viewer.getDividerLocation());
3632     AppVarna varna = new AppVarna(model, ap);
3633
3634     return varna;
3635   }
3636
3637   /**
3638    * Load any saved trees
3639    * 
3640    * @param jms
3641    * @param view
3642    * @param af
3643    * @param av
3644    * @param ap
3645    */
3646   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3647           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3648   {
3649     // TODO result of automated refactoring - are all these parameters needed?
3650     try
3651     {
3652       for (int t = 0; t < jms.getTreeCount(); t++)
3653       {
3654
3655         Tree tree = jms.getTree(t);
3656
3657         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3658         if (tp == null)
3659         {
3660           tp = af.showNewickTree(
3661                   new jalview.io.NewickFile(tree.getNewick()),
3662                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3663                   tree.getXpos(), tree.getYpos());
3664           if (tree.getId() != null)
3665           {
3666             // perhaps bind the tree id to something ?
3667           }
3668         }
3669         else
3670         {
3671           // update local tree attributes ?
3672           // TODO: should check if tp has been manipulated by user - if so its
3673           // settings shouldn't be modified
3674           tp.setTitle(tree.getTitle());
3675           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(),
3676                   tree.getWidth(), tree.getHeight()));
3677           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3678           // views'
3679           // works still
3680           tp.treeCanvas.av = av; // af.viewport;
3681           tp.treeCanvas.ap = ap; // af.alignPanel;
3682
3683         }
3684         if (tp == null)
3685         {
3686           warn("There was a problem recovering stored Newick tree: \n"
3687                   + tree.getNewick());
3688           continue;
3689         }
3690
3691         tp.fitToWindow.setState(tree.getFitToWindow());
3692         tp.fitToWindow_actionPerformed(null);
3693
3694         if (tree.getFontName() != null)
3695         {
3696           tp.setTreeFont(new java.awt.Font(tree.getFontName(),
3697                   tree.getFontStyle(), tree.getFontSize()));
3698         }
3699         else
3700         {
3701           tp.setTreeFont(new java.awt.Font(view.getFontName(),
3702                   view.getFontStyle(), tree.getFontSize()));
3703         }
3704
3705         tp.showPlaceholders(tree.getMarkUnlinked());
3706         tp.showBootstrap(tree.getShowBootstrap());
3707         tp.showDistances(tree.getShowDistances());
3708
3709         tp.treeCanvas.threshold = tree.getThreshold();
3710
3711         if (tree.getCurrentTree())
3712         {
3713           af.viewport.setCurrentTree(tp.getTree());
3714         }
3715       }
3716
3717     } catch (Exception ex)
3718     {
3719       ex.printStackTrace();
3720     }
3721   }
3722
3723   /**
3724    * Load and link any saved structure viewers.
3725    * 
3726    * @param jprovider
3727    * @param jseqs
3728    * @param af
3729    * @param ap
3730    */
3731   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3732           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3733   {
3734     /*
3735      * Run through all PDB ids on the alignment, and collect mappings between
3736      * distinct view ids and all sequences referring to that view.
3737      */
3738     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
3739
3740     for (int i = 0; i < jseqs.length; i++)
3741     {
3742       if (jseqs[i].getPdbidsCount() > 0)
3743       {
3744         Pdbids[] ids = jseqs[i].getPdbids();
3745         for (int p = 0; p < ids.length; p++)
3746         {
3747           final int structureStateCount = ids[p].getStructureStateCount();
3748           for (int s = 0; s < structureStateCount; s++)
3749           {
3750             // check to see if we haven't already created this structure view
3751             final StructureState structureState = ids[p]
3752                     .getStructureState(s);
3753             String sviewid = (structureState.getViewId() == null) ? null
3754                     : structureState.getViewId() + uniqueSetSuffix;
3755             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3756             // Originally : ids[p].getFile()
3757             // : TODO: verify external PDB file recovery still works in normal
3758             // jalview project load
3759             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId(),
3760                     ids[p].getFile()));
3761             jpdb.setId(ids[p].getId());
3762
3763             int x = structureState.getXpos();
3764             int y = structureState.getYpos();
3765             int width = structureState.getWidth();
3766             int height = structureState.getHeight();
3767
3768             // Probably don't need to do this anymore...
3769             // Desktop.desktop.getComponentAt(x, y);
3770             // TODO: NOW: check that this recovers the PDB file correctly.
3771             String pdbFile = loadPDBFile(jprovider, ids[p].getId(),
3772                     ids[p].getFile());
3773             jalview.datamodel.SequenceI seq = seqRefIds
3774                     .get(jseqs[i].getId() + "");
3775             if (sviewid == null)
3776             {
3777               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
3778                       + height;
3779             }
3780             if (!structureViewers.containsKey(sviewid))
3781             {
3782               structureViewers.put(sviewid,
3783                       new StructureViewerModel(x, y, width, height, false,
3784                               false, true, structureState.getViewId(),
3785                               structureState.getType()));
3786               // Legacy pre-2.7 conversion JAL-823 :
3787               // do not assume any view has to be linked for colour by
3788               // sequence
3789             }
3790
3791             // assemble String[] { pdb files }, String[] { id for each
3792             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3793             // seqs_file 2}, boolean[] {
3794             // linkAlignPanel,superposeWithAlignpanel}} from hash
3795             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3796             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3797                     | (structureState.hasAlignwithAlignPanel()
3798                             ? structureState.getAlignwithAlignPanel()
3799                             : false));
3800
3801             /*
3802              * Default colour by linked panel to false if not specified (e.g.
3803              * for pre-2.7 projects)
3804              */
3805             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3806             colourWithAlignPanel |= (structureState
3807                     .hasColourwithAlignPanel()
3808                             ? structureState.getColourwithAlignPanel()
3809                             : false);
3810             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3811
3812             /*
3813              * Default colour by viewer to true if not specified (e.g. for
3814              * pre-2.7 projects)
3815              */
3816             boolean colourByViewer = jmoldat.isColourByViewer();
3817             colourByViewer &= structureState.hasColourByJmol()
3818                     ? structureState.getColourByJmol()
3819                     : true;
3820             jmoldat.setColourByViewer(colourByViewer);
3821
3822             if (jmoldat.getStateData().length() < structureState
3823                     .getContent().length())
3824             {
3825               {
3826                 jmoldat.setStateData(structureState.getContent());
3827               }
3828             }
3829             if (ids[p].getFile() != null)
3830             {
3831               File mapkey = new File(ids[p].getFile());
3832               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3833               if (seqstrmaps == null)
3834               {
3835                 jmoldat.getFileData().put(mapkey,
3836                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3837                                 ids[p].getId()));
3838               }
3839               if (!seqstrmaps.getSeqList().contains(seq))
3840               {
3841                 seqstrmaps.getSeqList().add(seq);
3842                 // TODO and chains?
3843               }
3844             }
3845             else
3846             {
3847               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");
3848               warn(errorMessage);
3849             }
3850           }
3851         }
3852       }
3853     }
3854     // Instantiate the associated structure views
3855     for (Entry<String, StructureViewerModel> entry : structureViewers
3856             .entrySet())
3857     {
3858       try
3859       {
3860         createOrLinkStructureViewer(entry, af, ap, jprovider);
3861       } catch (Exception e)
3862       {
3863         System.err.println(
3864                 "Error loading structure viewer: " + e.getMessage());
3865         // failed - try the next one
3866       }
3867     }
3868   }
3869
3870   /**
3871    * 
3872    * @param viewerData
3873    * @param af
3874    * @param ap
3875    * @param jprovider
3876    */
3877   protected void createOrLinkStructureViewer(
3878           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3879           AlignmentPanel ap, jarInputStreamProvider jprovider)
3880   {
3881     final StructureViewerModel stateData = viewerData.getValue();
3882
3883     /*
3884      * Search for any viewer windows already open from other alignment views
3885      * that exactly match the stored structure state
3886      */
3887     StructureViewerBase comp = findMatchingViewer(viewerData);
3888
3889     if (comp != null)
3890     {
3891       linkStructureViewer(ap, comp, stateData);
3892       return;
3893     }
3894
3895     /*
3896      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3897      * "viewer_"+stateData.viewId
3898      */
3899     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3900     {
3901       createChimeraViewer(viewerData, af, jprovider);
3902     }
3903     else
3904     {
3905       /*
3906        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3907        */
3908       createJmolViewer(viewerData, af, jprovider);
3909     }
3910   }
3911
3912   /**
3913    * Create a new Chimera viewer.
3914    * 
3915    * @param data
3916    * @param af
3917    * @param jprovider
3918    */
3919   protected void createChimeraViewer(
3920           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3921           jarInputStreamProvider jprovider)
3922   {
3923     StructureViewerModel data = viewerData.getValue();
3924     String chimeraSessionFile = data.getStateData();
3925
3926     /*
3927      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3928      * 
3929      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3930      * 'uniquified' sviewid used to reconstruct the viewer here
3931      */
3932     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3933     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3934             "chimera", null);
3935
3936     Set<Entry<File, StructureData>> fileData = data.getFileData()
3937             .entrySet();
3938     List<PDBEntry> pdbs = new ArrayList<>();
3939     List<SequenceI[]> allseqs = new ArrayList<>();
3940     for (Entry<File, StructureData> pdb : fileData)
3941     {
3942       String filePath = pdb.getValue().getFilePath();
3943       String pdbId = pdb.getValue().getPdbId();
3944       // pdbs.add(new PDBEntry(filePath, pdbId));
3945       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3946       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3947       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3948       allseqs.add(seqs);
3949     }
3950
3951     boolean colourByChimera = data.isColourByViewer();
3952     boolean colourBySequence = data.isColourWithAlignPanel();
3953
3954     // TODO use StructureViewer as a factory here, see JAL-1761
3955     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3956     final SequenceI[][] seqsArray = allseqs
3957             .toArray(new SequenceI[allseqs.size()][]);
3958     String newViewId = viewerData.getKey();
3959
3960     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3961             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3962             colourBySequence, newViewId);
3963     cvf.setSize(data.getWidth(), data.getHeight());
3964     cvf.setLocation(data.getX(), data.getY());
3965   }
3966
3967   /**
3968    * Create a new Jmol window. First parse the Jmol state to translate filenames
3969    * loaded into the view, and record the order in which files are shown in the
3970    * Jmol view, so we can add the sequence mappings in same order.
3971    * 
3972    * @param viewerData
3973    * @param af
3974    * @param jprovider
3975    */
3976   protected void createJmolViewer(
3977           final Entry<String, StructureViewerModel> viewerData,
3978           AlignFrame af, jarInputStreamProvider jprovider)
3979   {
3980     final StructureViewerModel svattrib = viewerData.getValue();
3981     String state = svattrib.getStateData();
3982
3983     /*
3984      * Pre-2.9: state element value is the Jmol state string
3985      * 
3986      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3987      * + viewId
3988      */
3989     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3990     {
3991       state = readJarEntry(jprovider,
3992               getViewerJarEntryName(svattrib.getViewId()));
3993     }
3994
3995     List<String> pdbfilenames = new ArrayList<>();
3996     List<SequenceI[]> seqmaps = new ArrayList<>();
3997     List<String> pdbids = new ArrayList<>();
3998     StringBuilder newFileLoc = new StringBuilder(64);
3999     int cp = 0, ncp, ecp;
4000     Map<File, StructureData> oldFiles = svattrib.getFileData();
4001     while ((ncp = state.indexOf("load ", cp)) > -1)
4002     {
4003       do
4004       {
4005         // look for next filename in load statement
4006         newFileLoc.append(state.substring(cp,
4007                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4008         String oldfilenam = state.substring(ncp,
4009                 ecp = state.indexOf("\"", ncp));
4010         // recover the new mapping data for this old filename
4011         // have to normalize filename - since Jmol and jalview do
4012         // filename
4013         // translation differently.
4014         StructureData filedat = oldFiles.get(new File(oldfilenam));
4015         if (filedat == null)
4016         {
4017           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4018           filedat = oldFiles.get(new File(reformatedOldFilename));
4019         }
4020         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4021         pdbfilenames.add(filedat.getFilePath());
4022         pdbids.add(filedat.getPdbId());
4023         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4024         newFileLoc.append("\"");
4025         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4026                       // look for next file statement.
4027       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4028     }
4029     if (cp > 0)
4030     {
4031       // just append rest of state
4032       newFileLoc.append(state.substring(cp));
4033     }
4034     else
4035     {
4036       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4037       newFileLoc = new StringBuilder(state);
4038       newFileLoc.append("; load append ");
4039       for (File id : oldFiles.keySet())
4040       {
4041         // add this and any other pdb files that should be present in
4042         // the viewer
4043         StructureData filedat = oldFiles.get(id);
4044         newFileLoc.append(filedat.getFilePath());
4045         pdbfilenames.add(filedat.getFilePath());
4046         pdbids.add(filedat.getPdbId());
4047         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4048         newFileLoc.append(" \"");
4049         newFileLoc.append(filedat.getFilePath());
4050         newFileLoc.append("\"");
4051
4052       }
4053       newFileLoc.append(";");
4054     }
4055
4056     if (newFileLoc.length() == 0)
4057     {
4058       return;
4059     }
4060     int histbug = newFileLoc.indexOf("history = ");
4061     if (histbug > -1)
4062     {
4063       /*
4064        * change "history = [true|false];" to "history = [1|0];"
4065        */
4066       histbug += 10;
4067       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4068       String val = (diff == -1) ? null
4069               : newFileLoc.substring(histbug, diff);
4070       if (val != null && val.length() >= 4)
4071       {
4072         if (val.contains("e")) // eh? what can it be?
4073         {
4074           if (val.trim().equals("true"))
4075           {
4076             val = "1";
4077           }
4078           else
4079           {
4080             val = "0";
4081           }
4082           newFileLoc.replace(histbug, diff, val);
4083         }
4084       }
4085     }
4086
4087     final String[] pdbf = pdbfilenames
4088             .toArray(new String[pdbfilenames.size()]);
4089     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4090     final SequenceI[][] sq = seqmaps
4091             .toArray(new SequenceI[seqmaps.size()][]);
4092     final String fileloc = newFileLoc.toString();
4093     final String sviewid = viewerData.getKey();
4094     final AlignFrame alf = af;
4095     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4096             svattrib.getWidth(), svattrib.getHeight());
4097     try
4098     {
4099       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4100       {
4101         @Override
4102         public void run()
4103         {
4104           JalviewStructureDisplayI sview = null;
4105           try
4106           {
4107             sview = new StructureViewer(
4108                     alf.alignPanel.getStructureSelectionManager())
4109                             .createView(StructureViewer.ViewerType.JMOL,
4110                                     pdbf, id, sq, alf.alignPanel, svattrib,
4111                                     fileloc, rect, sviewid);
4112             addNewStructureViewer(sview);
4113           } catch (OutOfMemoryError ex)
4114           {
4115             new OOMWarning("restoring structure view for PDB id " + id,
4116                     (OutOfMemoryError) ex.getCause());
4117             if (sview != null && sview.isVisible())
4118             {
4119               sview.closeViewer(false);
4120               sview.setVisible(false);
4121               sview.dispose();
4122             }
4123           }
4124         }
4125       });
4126     } catch (InvocationTargetException ex)
4127     {
4128       warn("Unexpected error when opening Jmol view.", ex);
4129
4130     } catch (InterruptedException e)
4131     {
4132       // e.printStackTrace();
4133     }
4134
4135   }
4136
4137   /**
4138    * Generates a name for the entry in the project jar file to hold state
4139    * information for a structure viewer
4140    * 
4141    * @param viewId
4142    * @return
4143    */
4144   protected String getViewerJarEntryName(String viewId)
4145   {
4146     return VIEWER_PREFIX + viewId;
4147   }
4148
4149   /**
4150    * Returns any open frame that matches given structure viewer data. The match
4151    * is based on the unique viewId, or (for older project versions) the frame's
4152    * geometry.
4153    * 
4154    * @param viewerData
4155    * @return
4156    */
4157   protected StructureViewerBase findMatchingViewer(
4158           Entry<String, StructureViewerModel> viewerData)
4159   {
4160     final String sviewid = viewerData.getKey();
4161     final StructureViewerModel svattrib = viewerData.getValue();
4162     StructureViewerBase comp = null;
4163     JInternalFrame[] frames = getAllFrames();
4164     for (JInternalFrame frame : frames)
4165     {
4166       if (frame instanceof StructureViewerBase)
4167       {
4168         /*
4169          * Post jalview 2.4 schema includes structure view id
4170          */
4171         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4172                 .equals(sviewid))
4173         {
4174           comp = (StructureViewerBase) frame;
4175           break; // break added in 2.9
4176         }
4177         /*
4178          * Otherwise test for matching position and size of viewer frame
4179          */
4180         else if (frame.getX() == svattrib.getX()
4181                 && frame.getY() == svattrib.getY()
4182                 && frame.getHeight() == svattrib.getHeight()
4183                 && frame.getWidth() == svattrib.getWidth())
4184         {
4185           comp = (StructureViewerBase) frame;
4186           // no break in faint hope of an exact match on viewId
4187         }
4188       }
4189     }
4190     return comp;
4191   }
4192
4193   /**
4194    * Link an AlignmentPanel to an existing structure viewer.
4195    * 
4196    * @param ap
4197    * @param viewer
4198    * @param oldFiles
4199    * @param useinViewerSuperpos
4200    * @param usetoColourbyseq
4201    * @param viewerColouring
4202    */
4203   protected void linkStructureViewer(AlignmentPanel ap,
4204           StructureViewerBase viewer, StructureViewerModel stateData)
4205   {
4206     // NOTE: if the jalview project is part of a shared session then
4207     // view synchronization should/could be done here.
4208
4209     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4210     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4211     final boolean viewerColouring = stateData.isColourByViewer();
4212     Map<File, StructureData> oldFiles = stateData.getFileData();
4213
4214     /*
4215      * Add mapping for sequences in this view to an already open viewer
4216      */
4217     final AAStructureBindingModel binding = viewer.getBinding();
4218     for (File id : oldFiles.keySet())
4219     {
4220       // add this and any other pdb files that should be present in the
4221       // viewer
4222       StructureData filedat = oldFiles.get(id);
4223       String pdbFile = filedat.getFilePath();
4224       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4225       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4226               null);
4227       binding.addSequenceForStructFile(pdbFile, seq);
4228     }
4229     // and add the AlignmentPanel's reference to the view panel
4230     viewer.addAlignmentPanel(ap);
4231     if (useinViewerSuperpos)
4232     {
4233       viewer.useAlignmentPanelForSuperposition(ap);
4234     }
4235     else
4236     {
4237       viewer.excludeAlignmentPanelForSuperposition(ap);
4238     }
4239     if (usetoColourbyseq)
4240     {
4241       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4242     }
4243     else
4244     {
4245       viewer.excludeAlignmentPanelForColourbyseq(ap);
4246     }
4247   }
4248
4249   /**
4250    * Get all frames within the Desktop.
4251    * 
4252    * @return
4253    */
4254   protected JInternalFrame[] getAllFrames()
4255   {
4256     JInternalFrame[] frames = null;
4257     // TODO is this necessary - is it safe - risk of hanging?
4258     do
4259     {
4260       try
4261       {
4262         frames = Desktop.desktop.getAllFrames();
4263       } catch (ArrayIndexOutOfBoundsException e)
4264       {
4265         // occasional No such child exceptions are thrown here...
4266         try
4267         {
4268           Thread.sleep(10);
4269         } catch (InterruptedException f)
4270         {
4271         }
4272       }
4273     } while (frames == null);
4274     return frames;
4275   }
4276
4277   /**
4278    * Answers true if 'version' is equal to or later than 'supported', where each
4279    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4280    * changes. Development and test values for 'version' are leniently treated
4281    * i.e. answer true.
4282    * 
4283    * @param supported
4284    *          - minimum version we are comparing against
4285    * @param version
4286    *          - version of data being processsed
4287    * @return
4288    */
4289   public static boolean isVersionStringLaterThan(String supported,
4290           String version)
4291   {
4292     if (supported == null || version == null
4293             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4294             || version.equalsIgnoreCase("Test")
4295             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4296     {
4297       System.err.println("Assuming project file with "
4298               + (version == null ? "null" : version)
4299               + " is compatible with Jalview version " + supported);
4300       return true;
4301     }
4302     else
4303     {
4304       return StringUtils.compareVersions(version, supported, "b") >= 0;
4305     }
4306   }
4307
4308   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4309
4310   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4311   {
4312     if (newStructureViewers != null)
4313     {
4314       sview.getBinding().setFinishedLoadingFromArchive(false);
4315       newStructureViewers.add(sview);
4316     }
4317   }
4318
4319   protected void setLoadingFinishedForNewStructureViewers()
4320   {
4321     if (newStructureViewers != null)
4322     {
4323       for (JalviewStructureDisplayI sview : newStructureViewers)
4324       {
4325         sview.getBinding().setFinishedLoadingFromArchive(true);
4326       }
4327       newStructureViewers.clear();
4328       newStructureViewers = null;
4329     }
4330   }
4331
4332   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4333           List<SequenceI> hiddenSeqs, AlignmentI al,
4334           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4335           String viewId, List<JvAnnotRow> autoAlan)
4336   {
4337     AlignFrame af = null;
4338     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4339             uniqueSeqSetId, viewId);
4340
4341     af.setFileName(file, FileFormat.Jalview);
4342
4343     for (int i = 0; i < JSEQ.length; i++)
4344     {
4345       af.viewport.setSequenceColour(
4346               af.viewport.getAlignment().getSequenceAt(i),
4347               new java.awt.Color(JSEQ[i].getColour()));
4348     }
4349
4350     if (al.hasSeqrep())
4351     {
4352       af.getViewport().setColourByReferenceSeq(true);
4353       af.getViewport().setDisplayReferenceSeq(true);
4354     }
4355
4356     af.viewport.setGatherViewsHere(view.getGatheredViews());
4357
4358     if (view.getSequenceSetId() != null)
4359     {
4360       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4361
4362       af.viewport.setSequenceSetId(uniqueSeqSetId);
4363       if (av != null)
4364       {
4365         // propagate shared settings to this new view
4366         af.viewport.setHistoryList(av.getHistoryList());
4367         af.viewport.setRedoList(av.getRedoList());
4368       }
4369       else
4370       {
4371         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4372       }
4373       // TODO: check if this method can be called repeatedly without
4374       // side-effects if alignpanel already registered.
4375       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4376     }
4377     // apply Hidden regions to view.
4378     if (hiddenSeqs != null)
4379     {
4380       for (int s = 0; s < JSEQ.length; s++)
4381       {
4382         SequenceGroup hidden = new SequenceGroup();
4383         boolean isRepresentative = false;
4384         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4385         {
4386           isRepresentative = true;
4387           SequenceI sequenceToHide = al
4388                   .getSequenceAt(JSEQ[s].getHiddenSequences(r));
4389           hidden.addSequence(sequenceToHide, false);
4390           // remove from hiddenSeqs list so we don't try to hide it twice
4391           hiddenSeqs.remove(sequenceToHide);
4392         }
4393         if (isRepresentative)
4394         {
4395           SequenceI representativeSequence = al.getSequenceAt(s);
4396           hidden.addSequence(representativeSequence, false);
4397           af.viewport.hideRepSequences(representativeSequence, hidden);
4398         }
4399       }
4400
4401       SequenceI[] hseqs = hiddenSeqs
4402               .toArray(new SequenceI[hiddenSeqs.size()]);
4403       af.viewport.hideSequence(hseqs);
4404
4405     }
4406     // recover view properties and display parameters
4407
4408     af.viewport.setShowAnnotation(view.getShowAnnotation());
4409     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4410     af.viewport.setThreshold(view.getPidThreshold());
4411
4412     af.viewport.setColourText(view.getShowColourText());
4413
4414     af.viewport.setConservationSelected(view.getConservationSelected());
4415     af.viewport.setIncrement(view.getConsThreshold());
4416     af.viewport.setShowJVSuffix(view.getShowFullId());
4417     af.viewport.setRightAlignIds(view.getRightAlignIds());
4418     af.viewport.setFont(new java.awt.Font(view.getFontName(),
4419             view.getFontStyle(), view.getFontSize()), true);
4420     ViewStyleI vs = af.viewport.getViewStyle();
4421     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4422     af.viewport.setViewStyle(vs);
4423     // TODO: allow custom charWidth/Heights to be restored by updating them
4424     // after setting font - which means set above to false
4425     af.viewport.setRenderGaps(view.getRenderGaps());
4426     af.viewport.setWrapAlignment(view.getWrapAlignment());
4427     af.viewport.setShowAnnotation(view.getShowAnnotation());
4428
4429     af.viewport.setShowBoxes(view.getShowBoxes());
4430
4431     af.viewport.setShowText(view.getShowText());
4432
4433     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4434     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4435     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4436     af.viewport.setShowUnconserved(
4437             view.hasShowUnconserved() ? view.isShowUnconserved() : false);
4438     af.viewport.getRanges().setStartRes(view.getStartRes());
4439
4440     if (view.getViewName() != null)
4441     {
4442       af.viewport.viewName = view.getViewName();
4443       af.setInitialTabVisible();
4444     }
4445     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4446             view.getHeight());
4447     // startSeq set in af.alignPanel.updateLayout below
4448     af.alignPanel.updateLayout();
4449     ColourSchemeI cs = null;
4450     // apply colourschemes
4451     if (view.getBgColour() != null)
4452     {
4453       if (view.getBgColour().startsWith("ucs"))
4454       {
4455         cs = getUserColourScheme(jms, view.getBgColour());
4456       }
4457       else if (view.getBgColour().startsWith("Annotation"))
4458       {
4459         AnnotationColours viewAnnColour = view.getAnnotationColours();
4460         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4461
4462         // annpos
4463
4464       }
4465       else
4466       {
4467         cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour());
4468       }
4469     }
4470
4471     af.viewport.setGlobalColourScheme(cs);
4472     af.viewport.getResidueShading().setThreshold(view.getPidThreshold(),
4473             view.getIgnoreGapsinConsensus());
4474     af.viewport.getResidueShading()
4475             .setConsensus(af.viewport.getSequenceConsensusHash());
4476     af.viewport.setColourAppliesToAllGroups(false);
4477
4478     if (view.getConservationSelected() && cs != null)
4479     {
4480       af.viewport.getResidueShading()
4481               .setConservationInc(view.getConsThreshold());
4482     }
4483
4484     af.changeColour(cs);
4485
4486     af.viewport.setColourAppliesToAllGroups(true);
4487
4488     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4489
4490     if (view.hasCentreColumnLabels())
4491     {
4492       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4493     }
4494     if (view.hasIgnoreGapsinConsensus())
4495     {
4496       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4497               null);
4498     }
4499     if (view.hasFollowHighlight())
4500     {
4501       af.viewport.setFollowHighlight(view.getFollowHighlight());
4502     }
4503     if (view.hasFollowSelection())
4504     {
4505       af.viewport.followSelection = view.getFollowSelection();
4506     }
4507     if (view.hasShowConsensusHistogram())
4508     {
4509       af.viewport
4510               .setShowConsensusHistogram(view.getShowConsensusHistogram());
4511     }
4512     else
4513     {
4514       af.viewport.setShowConsensusHistogram(true);
4515     }
4516     if (view.hasShowSequenceLogo())
4517     {
4518       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4519     }
4520     else
4521     {
4522       af.viewport.setShowSequenceLogo(false);
4523     }
4524     if (view.hasNormaliseSequenceLogo())
4525     {
4526       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4527     }
4528     if (view.hasShowDbRefTooltip())
4529     {
4530       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4531     }
4532     if (view.hasShowNPfeatureTooltip())
4533     {
4534       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4535     }
4536     if (view.hasShowGroupConsensus())
4537     {
4538       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4539     }
4540     else
4541     {
4542       af.viewport.setShowGroupConsensus(false);
4543     }
4544     if (view.hasShowGroupConservation())
4545     {
4546       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4547     }
4548     else
4549     {
4550       af.viewport.setShowGroupConservation(false);
4551     }
4552
4553     // recover featre settings
4554     if (jms.getFeatureSettings() != null)
4555     {
4556       FeaturesDisplayed fdi;
4557       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4558       String[] renderOrder = new String[jms.getFeatureSettings()
4559               .getSettingCount()];
4560       Map<String, FeatureColourI> featureColours = new Hashtable<>();
4561       Map<String, Float> featureOrder = new Hashtable<>();
4562
4563       for (int fs = 0; fs < jms.getFeatureSettings()
4564               .getSettingCount(); fs++)
4565       {
4566         Setting setting = jms.getFeatureSettings().getSetting(fs);
4567         if (setting.hasMincolour())
4568         {
4569           FeatureColourI gc = setting.hasMin()
4570                   ? new FeatureColour(new Color(setting.getMincolour()),
4571                           new Color(setting.getColour()), setting.getMin(),
4572                           setting.getMax())
4573                   : new FeatureColour(new Color(setting.getMincolour()),
4574                           new Color(setting.getColour()), 0, 1);
4575           if (setting.hasThreshold())
4576           {
4577             gc.setThreshold(setting.getThreshold());
4578             int threshstate = setting.getThreshstate();
4579             // -1 = None, 0 = Below, 1 = Above threshold
4580             if (threshstate == 0)
4581             {
4582               gc.setBelowThreshold(true);
4583             }
4584             else if (threshstate == 1)
4585             {
4586               gc.setAboveThreshold(true);
4587             }
4588           }
4589           gc.setAutoScaled(true); // default
4590           if (setting.hasAutoScale())
4591           {
4592             gc.setAutoScaled(setting.getAutoScale());
4593           }
4594           if (setting.hasColourByLabel())
4595           {
4596             gc.setColourByLabel(setting.getColourByLabel());
4597           }
4598           // and put in the feature colour table.
4599           featureColours.put(setting.getType(), gc);
4600         }
4601         else
4602         {
4603           featureColours.put(setting.getType(),
4604                   new FeatureColour(new Color(setting.getColour())));
4605         }
4606         renderOrder[fs] = setting.getType();
4607         if (setting.hasOrder())
4608         {
4609           featureOrder.put(setting.getType(), setting.getOrder());
4610         }
4611         else
4612         {
4613           featureOrder.put(setting.getType(), new Float(
4614                   fs / jms.getFeatureSettings().getSettingCount()));
4615         }
4616         if (setting.getDisplay())
4617         {
4618           fdi.setVisible(setting.getType());
4619         }
4620       }
4621       Map<String, Boolean> fgtable = new Hashtable<>();
4622       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4623       {
4624         Group grp = jms.getFeatureSettings().getGroup(gs);
4625         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4626       }
4627       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4628       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4629       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4630       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4631               fgtable, featureColours, 1.0f, featureOrder);
4632       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4633               .transferSettings(frs);
4634
4635     }
4636
4637     if (view.getHiddenColumnsCount() > 0)
4638     {
4639       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4640       {
4641         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(),
4642                 view.getHiddenColumns(c).getEnd() // +1
4643         );
4644       }
4645     }
4646     if (view.getCalcIdParam() != null)
4647     {
4648       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4649       {
4650         if (calcIdParam != null)
4651         {
4652           if (recoverCalcIdParam(calcIdParam, af.viewport))
4653           {
4654           }
4655           else
4656           {
4657             warn("Couldn't recover parameters for "
4658                     + calcIdParam.getCalcId());
4659           }
4660         }
4661       }
4662     }
4663     af.setMenusFromViewport(af.viewport);
4664     af.setTitle(view.getTitle());
4665     // TODO: we don't need to do this if the viewport is aready visible.
4666     /*
4667      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4668      * has a 'cdna/protein complement' view, in which case save it in order to
4669      * populate a SplitFrame once all views have been read in.
4670      */
4671     String complementaryViewId = view.getComplementId();
4672     if (complementaryViewId == null)
4673     {
4674       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4675               view.getHeight());
4676       // recompute any autoannotation
4677       af.alignPanel.updateAnnotation(false, true);
4678       reorderAutoannotation(af, al, autoAlan);
4679       af.alignPanel.alignmentChanged();
4680     }
4681     else
4682     {
4683       splitFrameCandidates.put(view, af);
4684     }
4685     return af;
4686   }
4687
4688   /**
4689    * Reads saved data to restore Colour by Annotation settings
4690    * 
4691    * @param viewAnnColour
4692    * @param af
4693    * @param al
4694    * @param jms
4695    * @param checkGroupAnnColour
4696    * @return
4697    */
4698   private ColourSchemeI constructAnnotationColour(
4699           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
4700           JalviewModelSequence jms, boolean checkGroupAnnColour)
4701   {
4702     boolean propagateAnnColour = false;
4703     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4704     if (checkGroupAnnColour && al.getGroups() != null
4705             && al.getGroups().size() > 0)
4706     {
4707       // pre 2.8.1 behaviour
4708       // check to see if we should transfer annotation colours
4709       propagateAnnColour = true;
4710       for (SequenceGroup sg : al.getGroups())
4711       {
4712         if (sg.getColourScheme() instanceof AnnotationColourGradient)
4713         {
4714           propagateAnnColour = false;
4715         }
4716       }
4717     }
4718
4719     /*
4720      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
4721      */
4722     String annotationId = viewAnnColour.getAnnotation();
4723     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
4724
4725     /*
4726      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
4727      */
4728     if (matchedAnnotation == null
4729             && annAlignment.getAlignmentAnnotation() != null)
4730     {
4731       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4732       {
4733         if (annotationId
4734                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
4735         {
4736           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
4737           break;
4738         }
4739       }
4740     }
4741     if (matchedAnnotation == null)
4742     {
4743       System.err.println("Failed to match annotation colour scheme for "
4744               + annotationId);
4745       return null;
4746     }
4747     if (matchedAnnotation.getThreshold() == null)
4748     {
4749       matchedAnnotation.setThreshold(new GraphLine(
4750               viewAnnColour.getThreshold(), "Threshold", Color.black));
4751     }
4752
4753     AnnotationColourGradient cs = null;
4754     if (viewAnnColour.getColourScheme().equals("None"))
4755     {
4756       cs = new AnnotationColourGradient(matchedAnnotation,
4757               new Color(viewAnnColour.getMinColour()),
4758               new Color(viewAnnColour.getMaxColour()),
4759               viewAnnColour.getAboveThreshold());
4760     }
4761     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4762     {
4763       cs = new AnnotationColourGradient(matchedAnnotation,
4764               getUserColourScheme(jms, viewAnnColour.getColourScheme()),
4765               viewAnnColour.getAboveThreshold());
4766     }
4767     else
4768     {
4769       cs = new AnnotationColourGradient(matchedAnnotation,
4770               ColourSchemeProperty.getColourScheme(al,
4771                       viewAnnColour.getColourScheme()),
4772               viewAnnColour.getAboveThreshold());
4773     }
4774
4775     boolean perSequenceOnly = viewAnnColour.isPerSequence();
4776     boolean useOriginalColours = viewAnnColour.isPredefinedColours();
4777     cs.setSeqAssociated(perSequenceOnly);
4778     cs.setPredefinedColours(useOriginalColours);
4779
4780     if (propagateAnnColour && al.getGroups() != null)
4781     {
4782       // Also use these settings for all the groups
4783       for (int g = 0; g < al.getGroups().size(); g++)
4784       {
4785         SequenceGroup sg = al.getGroups().get(g);
4786         if (sg.getGroupColourScheme() == null)
4787         {
4788           continue;
4789         }
4790
4791         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
4792                 matchedAnnotation, sg.getColourScheme(),
4793                 viewAnnColour.getAboveThreshold());
4794         sg.setColourScheme(groupScheme);
4795         groupScheme.setSeqAssociated(perSequenceOnly);
4796         groupScheme.setPredefinedColours(useOriginalColours);
4797       }
4798     }
4799     return cs;
4800   }
4801
4802   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
4803           List<JvAnnotRow> autoAlan)
4804   {
4805     // copy over visualization settings for autocalculated annotation in the
4806     // view
4807     if (al.getAlignmentAnnotation() != null)
4808     {
4809       /**
4810        * Kludge for magic autoannotation names (see JAL-811)
4811        */
4812       String[] magicNames = new String[] { "Consensus", "Quality",
4813           "Conservation" };
4814       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4815       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
4816       for (String nm : magicNames)
4817       {
4818         visan.put(nm, nullAnnot);
4819       }
4820       for (JvAnnotRow auan : autoAlan)
4821       {
4822         visan.put(auan.template.label
4823                 + (auan.template.getCalcId() == null ? ""
4824                         : "\t" + auan.template.getCalcId()),
4825                 auan);
4826       }
4827       int hSize = al.getAlignmentAnnotation().length;
4828       List<JvAnnotRow> reorder = new ArrayList<>();
4829       // work through any autoCalculated annotation already on the view
4830       // removing it if it should be placed in a different location on the
4831       // annotation panel.
4832       List<String> remains = new ArrayList<>(visan.keySet());
4833       for (int h = 0; h < hSize; h++)
4834       {
4835         jalview.datamodel.AlignmentAnnotation jalan = al
4836                 .getAlignmentAnnotation()[h];
4837         if (jalan.autoCalculated)
4838         {
4839           String k;
4840           JvAnnotRow valan = visan.get(k = jalan.label);
4841           if (jalan.getCalcId() != null)
4842           {
4843             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4844           }
4845
4846           if (valan != null)
4847           {
4848             // delete the auto calculated row from the alignment
4849             al.deleteAnnotation(jalan, false);
4850             remains.remove(k);
4851             hSize--;
4852             h--;
4853             if (valan != nullAnnot)
4854             {
4855               if (jalan != valan.template)
4856               {
4857                 // newly created autoannotation row instance
4858                 // so keep a reference to the visible annotation row
4859                 // and copy over all relevant attributes
4860                 if (valan.template.graphHeight >= 0)
4861
4862                 {
4863                   jalan.graphHeight = valan.template.graphHeight;
4864                 }
4865                 jalan.visible = valan.template.visible;
4866               }
4867               reorder.add(new JvAnnotRow(valan.order, jalan));
4868             }
4869           }
4870         }
4871       }
4872       // Add any (possibly stale) autocalculated rows that were not appended to
4873       // the view during construction
4874       for (String other : remains)
4875       {
4876         JvAnnotRow othera = visan.get(other);
4877         if (othera != nullAnnot && othera.template.getCalcId() != null
4878                 && othera.template.getCalcId().length() > 0)
4879         {
4880           reorder.add(othera);
4881         }
4882       }
4883       // now put the automatic annotation in its correct place
4884       int s = 0, srt[] = new int[reorder.size()];
4885       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4886       for (JvAnnotRow jvar : reorder)
4887       {
4888         rws[s] = jvar;
4889         srt[s++] = jvar.order;
4890       }
4891       reorder.clear();
4892       jalview.util.QuickSort.sort(srt, rws);
4893       // and re-insert the annotation at its correct position
4894       for (JvAnnotRow jvar : rws)
4895       {
4896         al.addAnnotation(jvar.template, jvar.order);
4897       }
4898       af.alignPanel.adjustAnnotationHeight();
4899     }
4900   }
4901
4902   Hashtable skipList = null;
4903
4904   /**
4905    * TODO remove this method
4906    * 
4907    * @param view
4908    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4909    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4910    *         throw new Error("Implementation Error. No skipList defined for this
4911    *         Jalview2XML instance."); } return (AlignFrame)
4912    *         skipList.get(view.getSequenceSetId()); }
4913    */
4914
4915   /**
4916    * Check if the Jalview view contained in object should be skipped or not.
4917    * 
4918    * @param object
4919    * @return true if view's sequenceSetId is a key in skipList
4920    */
4921   private boolean skipViewport(JalviewModel object)
4922   {
4923     if (skipList == null)
4924     {
4925       return false;
4926     }
4927     String id;
4928     if (skipList.containsKey(
4929             id = object.getJalviewModelSequence().getViewport()[0]
4930                     .getSequenceSetId()))
4931     {
4932       if (Cache.log != null && Cache.log.isDebugEnabled())
4933       {
4934         Cache.log.debug("Skipping seuqence set id " + id);
4935       }
4936       return true;
4937     }
4938     return false;
4939   }
4940
4941   public void addToSkipList(AlignFrame af)
4942   {
4943     if (skipList == null)
4944     {
4945       skipList = new Hashtable();
4946     }
4947     skipList.put(af.getViewport().getSequenceSetId(), af);
4948   }
4949
4950   public void clearSkipList()
4951   {
4952     if (skipList != null)
4953     {
4954       skipList.clear();
4955       skipList = null;
4956     }
4957   }
4958
4959   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
4960           boolean ignoreUnrefed)
4961   {
4962     jalview.datamodel.AlignmentI ds = getDatasetFor(
4963             vamsasSet.getDatasetId());
4964     Vector dseqs = null;
4965     if (ds == null)
4966     {
4967       // create a list of new dataset sequences
4968       dseqs = new Vector();
4969     }
4970     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4971     {
4972       Sequence vamsasSeq = vamsasSet.getSequence(i);
4973       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
4974     }
4975     // create a new dataset
4976     if (ds == null)
4977     {
4978       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4979       dseqs.copyInto(dsseqs);
4980       ds = new jalview.datamodel.Alignment(dsseqs);
4981       debug("Created new dataset " + vamsasSet.getDatasetId()
4982               + " for alignment " + System.identityHashCode(al));
4983       addDatasetRef(vamsasSet.getDatasetId(), ds);
4984     }
4985     // set the dataset for the newly imported alignment.
4986     if (al.getDataset() == null && !ignoreUnrefed)
4987     {
4988       al.setDataset(ds);
4989     }
4990   }
4991
4992   /**
4993    * 
4994    * @param vamsasSeq
4995    *          sequence definition to create/merge dataset sequence for
4996    * @param ds
4997    *          dataset alignment
4998    * @param dseqs
4999    *          vector to add new dataset sequence to
5000    * @param ignoreUnrefed
5001    *          - when true, don't create new sequences from vamsasSeq if it's id
5002    *          doesn't already have an asssociated Jalview sequence.
5003    * @param vseqpos
5004    *          - used to reorder the sequence in the alignment according to the
5005    *          vamsasSeq array ordering, to preserve ordering of dataset
5006    */
5007   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5008           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5009   {
5010     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5011     // xRef Codon Maps
5012     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5013     boolean reorder = false;
5014     SequenceI dsq = null;
5015     if (sq != null && sq.getDatasetSequence() != null)
5016     {
5017       dsq = sq.getDatasetSequence();
5018     }
5019     else
5020     {
5021       reorder = true;
5022     }
5023     if (sq == null && ignoreUnrefed)
5024     {
5025       return;
5026     }
5027     String sqid = vamsasSeq.getDsseqid();
5028     if (dsq == null)
5029     {
5030       // need to create or add a new dataset sequence reference to this sequence
5031       if (sqid != null)
5032       {
5033         dsq = seqRefIds.get(sqid);
5034       }
5035       // check again
5036       if (dsq == null)
5037       {
5038         // make a new dataset sequence
5039         dsq = sq.createDatasetSequence();
5040         if (sqid == null)
5041         {
5042           // make up a new dataset reference for this sequence
5043           sqid = seqHash(dsq);
5044         }
5045         dsq.setVamsasId(uniqueSetSuffix + sqid);
5046         seqRefIds.put(sqid, dsq);
5047         if (ds == null)
5048         {
5049           if (dseqs != null)
5050           {
5051             dseqs.addElement(dsq);
5052           }
5053         }
5054         else
5055         {
5056           ds.addSequence(dsq);
5057         }
5058       }
5059       else
5060       {
5061         if (sq != dsq)
5062         { // make this dataset sequence sq's dataset sequence
5063           sq.setDatasetSequence(dsq);
5064           // and update the current dataset alignment
5065           if (ds == null)
5066           {
5067             if (dseqs != null)
5068             {
5069               if (!dseqs.contains(dsq))
5070               {
5071                 dseqs.add(dsq);
5072               }
5073             }
5074             else
5075             {
5076               if (ds.findIndex(dsq) < 0)
5077               {
5078                 ds.addSequence(dsq);
5079               }
5080             }
5081           }
5082         }
5083       }
5084     }
5085     // TODO: refactor this as a merge dataset sequence function
5086     // now check that sq (the dataset sequence) sequence really is the union of
5087     // all references to it
5088     // boolean pre = sq.getStart() < dsq.getStart();
5089     // boolean post = sq.getEnd() > dsq.getEnd();
5090     // if (pre || post)
5091     if (sq != dsq)
5092     {
5093       // StringBuffer sb = new StringBuffer();
5094       String newres = jalview.analysis.AlignSeq.extractGaps(
5095               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5096       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5097               && newres.length() > dsq.getLength())
5098       {
5099         // Update with the longer sequence.
5100         synchronized (dsq)
5101         {
5102           /*
5103            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5104            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5105            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5106            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5107            */
5108           dsq.setSequence(newres);
5109         }
5110         // TODO: merges will never happen if we 'know' we have the real dataset
5111         // sequence - this should be detected when id==dssid
5112         System.err.println(
5113                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5114         // + (pre ? "prepended" : "") + " "
5115         // + (post ? "appended" : ""));
5116       }
5117     }
5118     else
5119     {
5120       // sequence refs are identical. We may need to update the existing dataset
5121       // alignment with this one, though.
5122       if (ds != null && dseqs == null)
5123       {
5124         int opos = ds.findIndex(dsq);
5125         SequenceI tseq = null;
5126         if (opos != -1 && vseqpos != opos)
5127         {
5128           // remove from old position
5129           ds.deleteSequence(dsq);
5130         }
5131         if (vseqpos < ds.getHeight())
5132         {
5133           if (vseqpos != opos)
5134           {
5135             // save sequence at destination position
5136             tseq = ds.getSequenceAt(vseqpos);
5137             ds.replaceSequenceAt(vseqpos, dsq);
5138             ds.addSequence(tseq);
5139           }
5140         }
5141         else
5142         {
5143           ds.addSequence(dsq);
5144         }
5145       }
5146     }
5147   }
5148
5149   /*
5150    * TODO use AlignmentI here and in related methods - needs
5151    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5152    */
5153   Hashtable<String, AlignmentI> datasetIds = null;
5154
5155   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5156
5157   private AlignmentI getDatasetFor(String datasetId)
5158   {
5159     if (datasetIds == null)
5160     {
5161       datasetIds = new Hashtable<>();
5162       return null;
5163     }
5164     if (datasetIds.containsKey(datasetId))
5165     {
5166       return datasetIds.get(datasetId);
5167     }
5168     return null;
5169   }
5170
5171   private void addDatasetRef(String datasetId, AlignmentI dataset)
5172   {
5173     if (datasetIds == null)
5174     {
5175       datasetIds = new Hashtable<>();
5176     }
5177     datasetIds.put(datasetId, dataset);
5178   }
5179
5180   /**
5181    * make a new dataset ID for this jalview dataset alignment
5182    * 
5183    * @param dataset
5184    * @return
5185    */
5186   private String getDatasetIdRef(AlignmentI dataset)
5187   {
5188     if (dataset.getDataset() != null)
5189     {
5190       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5191     }
5192     String datasetId = makeHashCode(dataset, null);
5193     if (datasetId == null)
5194     {
5195       // make a new datasetId and record it
5196       if (dataset2Ids == null)
5197       {
5198         dataset2Ids = new IdentityHashMap<>();
5199       }
5200       else
5201       {
5202         datasetId = dataset2Ids.get(dataset);
5203       }
5204       if (datasetId == null)
5205       {
5206         datasetId = "ds" + dataset2Ids.size() + 1;
5207         dataset2Ids.put(dataset, datasetId);
5208       }
5209     }
5210     return datasetId;
5211   }
5212
5213   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5214   {
5215     for (int d = 0; d < sequence.getDBRefCount(); d++)
5216     {
5217       DBRef dr = sequence.getDBRef(d);
5218       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5219               sequence.getDBRef(d).getSource(),
5220               sequence.getDBRef(d).getVersion(),
5221               sequence.getDBRef(d).getAccessionId());
5222       if (dr.getMapping() != null)
5223       {
5224         entry.setMap(addMapping(dr.getMapping()));
5225       }
5226       datasetSequence.addDBRef(entry);
5227     }
5228   }
5229
5230   private jalview.datamodel.Mapping addMapping(Mapping m)
5231   {
5232     SequenceI dsto = null;
5233     // Mapping m = dr.getMapping();
5234     int fr[] = new int[m.getMapListFromCount() * 2];
5235     Enumeration f = m.enumerateMapListFrom();
5236     for (int _i = 0; f.hasMoreElements(); _i += 2)
5237     {
5238       MapListFrom mf = (MapListFrom) f.nextElement();
5239       fr[_i] = mf.getStart();
5240       fr[_i + 1] = mf.getEnd();
5241     }
5242     int fto[] = new int[m.getMapListToCount() * 2];
5243     f = m.enumerateMapListTo();
5244     for (int _i = 0; f.hasMoreElements(); _i += 2)
5245     {
5246       MapListTo mf = (MapListTo) f.nextElement();
5247       fto[_i] = mf.getStart();
5248       fto[_i + 1] = mf.getEnd();
5249     }
5250     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5251             fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
5252     if (m.getMappingChoice() != null)
5253     {
5254       MappingChoice mc = m.getMappingChoice();
5255       if (mc.getDseqFor() != null)
5256       {
5257         String dsfor = "" + mc.getDseqFor();
5258         if (seqRefIds.containsKey(dsfor))
5259         {
5260           /**
5261            * recover from hash
5262            */
5263           jmap.setTo(seqRefIds.get(dsfor));
5264         }
5265         else
5266         {
5267           frefedSequence.add(newMappingRef(dsfor, jmap));
5268         }
5269       }
5270       else
5271       {
5272         /**
5273          * local sequence definition
5274          */
5275         Sequence ms = mc.getSequence();
5276         SequenceI djs = null;
5277         String sqid = ms.getDsseqid();
5278         if (sqid != null && sqid.length() > 0)
5279         {
5280           /*
5281            * recover dataset sequence
5282            */
5283           djs = seqRefIds.get(sqid);
5284         }
5285         else
5286         {
5287           System.err.println(
5288                   "Warning - making up dataset sequence id for DbRef sequence map reference");
5289           sqid = ((Object) ms).toString(); // make up a new hascode for
5290           // undefined dataset sequence hash
5291           // (unlikely to happen)
5292         }
5293
5294         if (djs == null)
5295         {
5296           /**
5297            * make a new dataset sequence and add it to refIds hash
5298            */
5299           djs = new jalview.datamodel.Sequence(ms.getName(),
5300                   ms.getSequence());
5301           djs.setStart(jmap.getMap().getToLowest());
5302           djs.setEnd(jmap.getMap().getToHighest());
5303           djs.setVamsasId(uniqueSetSuffix + sqid);
5304           jmap.setTo(djs);
5305           incompleteSeqs.put(sqid, djs);
5306           seqRefIds.put(sqid, djs);
5307
5308         }
5309         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5310         addDBRefs(djs, ms);
5311
5312       }
5313     }
5314     return (jmap);
5315
5316   }
5317
5318   /**
5319    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
5320    * view as XML (but not to file), and then reloading it
5321    * 
5322    * @param ap
5323    * @return
5324    */
5325   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
5326   {
5327     initSeqRefs();
5328     JalviewModel jm = saveState(ap, null, null, null);
5329
5330     uniqueSetSuffix = "";
5331     jm.getJalviewModelSequence().getViewport(0).setId(null);
5332     // we don't overwrite the view we just copied
5333
5334     if (this.frefedSequence == null)
5335     {
5336       frefedSequence = new Vector<SeqFref>();
5337     }
5338
5339     viewportsAdded.clear();
5340
5341     AlignFrame af = loadFromObject(jm, null, false, null);
5342     af.alignPanels.clear();
5343     af.closeMenuItem_actionPerformed(true);
5344
5345     /*
5346      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5347      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5348      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5349      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5350      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5351      */
5352
5353     return af.alignPanel;
5354   }
5355
5356   private Hashtable jvids2vobj;
5357
5358   private void warn(String msg)
5359   {
5360     warn(msg, null);
5361   }
5362
5363   private void warn(String msg, Exception e)
5364   {
5365     if (Cache.log != null)
5366     {
5367       if (e != null)
5368       {
5369         Cache.log.warn(msg, e);
5370       }
5371       else
5372       {
5373         Cache.log.warn(msg);
5374       }
5375     }
5376     else
5377     {
5378       System.err.println("Warning: " + msg);
5379       if (e != null)
5380       {
5381         e.printStackTrace();
5382       }
5383     }
5384   }
5385
5386   private void debug(String string)
5387   {
5388     debug(string, null);
5389   }
5390
5391   private void debug(String msg, Exception e)
5392   {
5393     if (Cache.log != null)
5394     {
5395       if (e != null)
5396       {
5397         Cache.log.debug(msg, e);
5398       }
5399       else
5400       {
5401         Cache.log.debug(msg);
5402       }
5403     }
5404     else
5405     {
5406       System.err.println("Warning: " + msg);
5407       if (e != null)
5408       {
5409         e.printStackTrace();
5410       }
5411     }
5412   }
5413
5414   /**
5415    * set the object to ID mapping tables used to write/recover objects and XML
5416    * ID strings for the jalview project. If external tables are provided then
5417    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5418    * object goes out of scope. - also populates the datasetIds hashtable with
5419    * alignment objects containing dataset sequences
5420    * 
5421    * @param vobj2jv
5422    *          Map from ID strings to jalview datamodel
5423    * @param jv2vobj
5424    *          Map from jalview datamodel to ID strings
5425    * 
5426    * 
5427    */
5428   public void setObjectMappingTables(Hashtable vobj2jv,
5429           IdentityHashMap jv2vobj)
5430   {
5431     this.jv2vobj = jv2vobj;
5432     this.vobj2jv = vobj2jv;
5433     Iterator ds = jv2vobj.keySet().iterator();
5434     String id;
5435     while (ds.hasNext())
5436     {
5437       Object jvobj = ds.next();
5438       id = jv2vobj.get(jvobj).toString();
5439       if (jvobj instanceof jalview.datamodel.Alignment)
5440       {
5441         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5442         {
5443           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5444         }
5445       }
5446       else if (jvobj instanceof jalview.datamodel.Sequence)
5447       {
5448         // register sequence object so the XML parser can recover it.
5449         if (seqRefIds == null)
5450         {
5451           seqRefIds = new HashMap<>();
5452         }
5453         if (seqsToIds == null)
5454         {
5455           seqsToIds = new IdentityHashMap<>();
5456         }
5457         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5458         seqsToIds.put((SequenceI) jvobj, id);
5459       }
5460       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5461       {
5462         String anid;
5463         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5464         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5465         if (jvann.annotationId == null)
5466         {
5467           jvann.annotationId = anid;
5468         }
5469         if (!jvann.annotationId.equals(anid))
5470         {
5471           // TODO verify that this is the correct behaviour
5472           this.warn("Overriding Annotation ID for " + anid
5473                   + " from different id : " + jvann.annotationId);
5474           jvann.annotationId = anid;
5475         }
5476       }
5477       else if (jvobj instanceof String)
5478       {
5479         if (jvids2vobj == null)
5480         {
5481           jvids2vobj = new Hashtable();
5482           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5483         }
5484       }
5485       else
5486       {
5487         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5488       }
5489     }
5490   }
5491
5492   /**
5493    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5494    * objects created from the project archive. If string is null (default for
5495    * construction) then suffix will be set automatically.
5496    * 
5497    * @param string
5498    */
5499   public void setUniqueSetSuffix(String string)
5500   {
5501     uniqueSetSuffix = string;
5502
5503   }
5504
5505   /**
5506    * uses skipList2 as the skipList for skipping views on sequence sets
5507    * associated with keys in the skipList
5508    * 
5509    * @param skipList2
5510    */
5511   public void setSkipList(Hashtable skipList2)
5512   {
5513     skipList = skipList2;
5514   }
5515
5516   /**
5517    * Reads the jar entry of given name and returns its contents, or null if the
5518    * entry is not found.
5519    * 
5520    * @param jprovider
5521    * @param jarEntryName
5522    * @return
5523    */
5524   protected String readJarEntry(jarInputStreamProvider jprovider,
5525           String jarEntryName)
5526   {
5527     String result = null;
5528     BufferedReader in = null;
5529
5530     try
5531     {
5532       /*
5533        * Reopen the jar input stream and traverse its entries to find a matching
5534        * name
5535        */
5536       JarInputStream jin = jprovider.getJarInputStream();
5537       JarEntry entry = null;
5538       do
5539       {
5540         entry = jin.getNextJarEntry();
5541       } while (entry != null && !entry.getName().equals(jarEntryName));
5542
5543       if (entry != null)
5544       {
5545         StringBuilder out = new StringBuilder(256);
5546         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5547         String data;
5548
5549         while ((data = in.readLine()) != null)
5550         {
5551           out.append(data);
5552         }
5553         result = out.toString();
5554       }
5555       else
5556       {
5557         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5558       }
5559     } catch (Exception ex)
5560     {
5561       ex.printStackTrace();
5562     } finally
5563     {
5564       if (in != null)
5565       {
5566         try
5567         {
5568           in.close();
5569         } catch (IOException e)
5570         {
5571           // ignore
5572         }
5573       }
5574     }
5575
5576     return result;
5577   }
5578
5579   /**
5580    * Returns an incrementing counter (0, 1, 2...)
5581    * 
5582    * @return
5583    */
5584   private synchronized int nextCounter()
5585   {
5586     return counter++;
5587   }
5588 }