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