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