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