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