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