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