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