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