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