JAL-1834 removed quotes around truncated strings with ellipsis
[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.getDatasetSequence();
680       String id = seqHash(jds);
681
682       if (seqRefIds.get(id) != null)
683       {
684         // This happens for two reasons: 1. multiple views are being serialised.
685         // 2. the hashCode has collided with another sequence's code. This DOES
686         // HAPPEN! (PF00072.15.stk does this)
687         // JBPNote: Uncomment to debug writing out of files that do not read
688         // back in due to ArrayOutOfBoundExceptions.
689         // System.err.println("vamsasSeq backref: "+id+"");
690         // System.err.println(jds.getName()+"
691         // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
692         // System.err.println("Hashcode: "+seqHash(jds));
693         // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
694         // System.err.println(rsq.getName()+"
695         // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
696         // System.err.println("Hashcode: "+seqHash(rsq));
697       }
698       else
699       {
700         vamsasSeq = createVamsasSequence(id, jds);
701         vamsasSet.addSequence(vamsasSeq);
702         seqRefIds.put(id, jds);
703       }
704
705       jseq = new JSeq();
706       jseq.setStart(jds.getStart());
707       jseq.setEnd(jds.getEnd());
708       jseq.setColour(av.getSequenceColour(jds).getRGB());
709
710       jseq.setId(id); // jseq id should be a string not a number
711       if (!storeDS)
712       {
713         // Store any sequences this sequence represents
714         if (av.hasHiddenRows())
715         {
716           jseq.setHidden(av.getAlignment().getHiddenSequences()
717                   .isHidden(jds));
718
719           if (av.isHiddenRepSequence(jal.getSequenceAt(i)))
720           {
721             jalview.datamodel.SequenceI[] reps = av
722                     .getRepresentedSequences(jal.getSequenceAt(i))
723                     .getSequencesInOrder(jal);
724
725             for (int h = 0; h < reps.length; h++)
726             {
727               if (reps[h] != jal.getSequenceAt(i))
728               {
729                 jseq.addHiddenSequences(jal.findIndex(reps[h]));
730               }
731             }
732           }
733         }
734       }
735
736       if (jds.getSequenceFeatures() != null)
737       {
738         jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
739         int index = 0;
740         while (index < sf.length)
741         {
742           Features features = new Features();
743
744           features.setBegin(sf[index].getBegin());
745           features.setEnd(sf[index].getEnd());
746           features.setDescription(sf[index].getDescription());
747           features.setType(sf[index].getType());
748           features.setFeatureGroup(sf[index].getFeatureGroup());
749           features.setScore(sf[index].getScore());
750           if (sf[index].links != null)
751           {
752             for (int l = 0; l < sf[index].links.size(); l++)
753             {
754               OtherData keyValue = new OtherData();
755               keyValue.setKey("LINK_" + l);
756               keyValue.setValue(sf[index].links.elementAt(l).toString());
757               features.addOtherData(keyValue);
758             }
759           }
760           if (sf[index].otherDetails != null)
761           {
762             String key;
763             Enumeration keys = sf[index].otherDetails.keys();
764             while (keys.hasMoreElements())
765             {
766               key = keys.nextElement().toString();
767               OtherData keyValue = new OtherData();
768               keyValue.setKey(key);
769               keyValue.setValue(sf[index].otherDetails.get(key).toString());
770               features.addOtherData(keyValue);
771             }
772           }
773
774           jseq.addFeatures(features);
775           index++;
776         }
777       }
778
779       if (jdatasq.getAllPDBEntries() != null)
780       {
781         Enumeration en = jdatasq.getAllPDBEntries().elements();
782         while (en.hasMoreElements())
783         {
784           Pdbids pdb = new Pdbids();
785           jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en
786                   .nextElement();
787
788           String pdbId = entry.getId();
789           pdb.setId(pdbId);
790           pdb.setType(entry.getType());
791
792           /*
793            * Store any structure views associated with this sequence. This
794            * section copes with duplicate entries in the project, so a dataset
795            * only view *should* be coped with sensibly.
796            */
797           // This must have been loaded, is it still visible?
798           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
799           String matchedFile = null;
800           for (int f = frames.length - 1; f > -1; f--)
801           {
802             if (frames[f] instanceof StructureViewerBase)
803             {
804               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
805               matchedFile = saveStructureState(ap, jds, pdb, entry,
806                       viewIds, matchedFile, viewFrame);
807               /*
808                * Only store each structure viewer's state once in the project
809                * jar. First time through only (storeDS==false)
810                */
811               String viewId = viewFrame.getViewId();
812               if (!storeDS && !viewIds.contains(viewId))
813               {
814                 viewIds.add(viewId);
815                 try
816                 {
817                   String viewerState = viewFrame.getStateInfo();
818                   writeJarEntry(jout, getViewerJarEntryName(viewId),
819                           viewerState.getBytes());
820                 } catch (IOException e)
821                 {
822                   System.err.println("Error saving viewer state: "
823                           + e.getMessage());
824                 }
825               }
826             }
827           }
828
829           if (matchedFile != null || entry.getFile() != null)
830           {
831             if (entry.getFile() != null)
832             {
833               // use entry's file
834               matchedFile = entry.getFile();
835             }
836             pdb.setFile(matchedFile); // entry.getFile());
837             if (pdbfiles == null)
838             {
839               pdbfiles = new ArrayList<String>();
840             }
841
842             if (!pdbfiles.contains(pdbId))
843             {
844               pdbfiles.add(pdbId);
845               copyFileToJar(jout, matchedFile, pdbId);
846             }
847           }
848
849           if (entry.getProperty() != null && !entry.getProperty().isEmpty())
850           {
851             PdbentryItem item = new PdbentryItem();
852             Hashtable properties = entry.getProperty();
853             Enumeration en2 = properties.keys();
854             while (en2.hasMoreElements())
855             {
856               Property prop = new Property();
857               String key = en2.nextElement().toString();
858               prop.setName(key);
859               prop.setValue(properties.get(key).toString());
860               item.addProperty(prop);
861             }
862             pdb.addPdbentryItem(item);
863           }
864
865           jseq.addPdbids(pdb);
866         }
867       }
868
869       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
870
871       jms.addJSeq(jseq);
872     }
873
874     if (!storeDS && av.hasHiddenRows())
875     {
876       jal = av.getAlignment();
877     }
878     // SAVE MAPPINGS
879     if (jal.getCodonFrames() != null)
880     {
881       Set<AlignedCodonFrame> jac = jal.getCodonFrames();
882       for (AlignedCodonFrame acf : jac)
883       {
884         AlcodonFrame alc = new AlcodonFrame();
885         vamsasSet.addAlcodonFrame(alc);
886         if (acf.getProtMappings() != null
887                 && acf.getProtMappings().length > 0)
888         {
889           SequenceI[] dnas = acf.getdnaSeqs();
890           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
891           for (int m = 0; m < pmaps.length; m++)
892           {
893             AlcodMap alcmap = new AlcodMap();
894             alcmap.setDnasq(seqHash(dnas[m]));
895             alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
896                     false));
897             alc.addAlcodMap(alcmap);
898           }
899         }
900
901         // {
902         // AlcodonFrame alc = new AlcodonFrame();
903         // vamsasSet.addAlcodonFrame(alc);
904         // for (int p = 0; p < acf.aaWidth; p++)
905         // {
906         // Alcodon cmap = new Alcodon();
907         // if (acf.codons[p] != null)
908         // {
909         // // Null codons indicate a gapped column in the translated peptide
910         // // alignment.
911         // cmap.setPos1(acf.codons[p][0]);
912         // cmap.setPos2(acf.codons[p][1]);
913         // cmap.setPos3(acf.codons[p][2]);
914         // }
915         // alc.addAlcodon(cmap);
916         // }
917         // if (acf.getProtMappings() != null
918         // && acf.getProtMappings().length > 0)
919         // {
920         // SequenceI[] dnas = acf.getdnaSeqs();
921         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
922         // for (int m = 0; m < pmaps.length; m++)
923         // {
924         // AlcodMap alcmap = new AlcodMap();
925         // alcmap.setDnasq(seqHash(dnas[m]));
926         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
927         // false));
928         // alc.addAlcodMap(alcmap);
929         // }
930         // }
931       }
932     }
933
934     // SAVE TREES
935     // /////////////////////////////////
936     if (!storeDS && av.currentTree != null)
937     {
938       // FIND ANY ASSOCIATED TREES
939       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
940       if (Desktop.desktop != null)
941       {
942         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
943
944         for (int t = 0; t < frames.length; t++)
945         {
946           if (frames[t] instanceof TreePanel)
947           {
948             TreePanel tp = (TreePanel) frames[t];
949
950             if (tp.treeCanvas.av.getAlignment() == jal)
951             {
952               Tree tree = new Tree();
953               tree.setTitle(tp.getTitle());
954               tree.setCurrentTree((av.currentTree == tp.getTree()));
955               tree.setNewick(tp.getTree().toString());
956               tree.setThreshold(tp.treeCanvas.threshold);
957
958               tree.setFitToWindow(tp.fitToWindow.getState());
959               tree.setFontName(tp.getTreeFont().getName());
960               tree.setFontSize(tp.getTreeFont().getSize());
961               tree.setFontStyle(tp.getTreeFont().getStyle());
962               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
963
964               tree.setShowBootstrap(tp.bootstrapMenu.getState());
965               tree.setShowDistances(tp.distanceMenu.getState());
966
967               tree.setHeight(tp.getHeight());
968               tree.setWidth(tp.getWidth());
969               tree.setXpos(tp.getX());
970               tree.setYpos(tp.getY());
971               tree.setId(makeHashCode(tp, null));
972               jms.addTree(tree);
973             }
974           }
975         }
976       }
977     }
978
979     // SAVE ANNOTATIONS
980     /**
981      * store forward refs from an annotationRow to any groups
982      */
983     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<SequenceGroup, String>();
984     if (storeDS)
985     {
986       for (SequenceI sq : jal.getSequences())
987       {
988         // Store annotation on dataset sequences only
989         AlignmentAnnotation[] aa = sq.getAnnotation();
990         if (aa != null && aa.length > 0)
991         {
992           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
993                   vamsasSet);
994         }
995       }
996     }
997     else
998     {
999       if (jal.getAlignmentAnnotation() != null)
1000       {
1001         // Store the annotation shown on the alignment.
1002         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
1003         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1004                 vamsasSet);
1005       }
1006     }
1007     // SAVE GROUPS
1008     if (jal.getGroups() != null)
1009     {
1010       JGroup[] groups = new JGroup[jal.getGroups().size()];
1011       int i = -1;
1012       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
1013       {
1014         JGroup jGroup = new JGroup();
1015         groups[++i] = jGroup;
1016
1017         jGroup.setStart(sg.getStartRes());
1018         jGroup.setEnd(sg.getEndRes());
1019         jGroup.setName(sg.getName());
1020         if (groupRefs.containsKey(sg))
1021         {
1022           // group has references so set its ID field
1023           jGroup.setId(groupRefs.get(sg));
1024         }
1025         if (sg.cs != null)
1026         {
1027           if (sg.cs.conservationApplied())
1028           {
1029             jGroup.setConsThreshold(sg.cs.getConservationInc());
1030
1031             if (sg.cs instanceof jalview.schemes.UserColourScheme)
1032             {
1033               jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1034             }
1035             else
1036             {
1037               jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1038             }
1039           }
1040           else if (sg.cs instanceof jalview.schemes.AnnotationColourGradient)
1041           {
1042             jGroup.setColour("AnnotationColourGradient");
1043             jGroup.setAnnotationColours(constructAnnotationColours(
1044                     (jalview.schemes.AnnotationColourGradient) sg.cs,
1045                     userColours, jms));
1046           }
1047           else if (sg.cs instanceof jalview.schemes.UserColourScheme)
1048           {
1049             jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1050           }
1051           else
1052           {
1053             jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1054           }
1055
1056           jGroup.setPidThreshold(sg.cs.getThreshold());
1057         }
1058
1059         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1060         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1061         jGroup.setDisplayText(sg.getDisplayText());
1062         jGroup.setColourText(sg.getColourText());
1063         jGroup.setTextCol1(sg.textColour.getRGB());
1064         jGroup.setTextCol2(sg.textColour2.getRGB());
1065         jGroup.setTextColThreshold(sg.thresholdTextColour);
1066         jGroup.setShowUnconserved(sg.getShowNonconserved());
1067         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1068         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1069         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1070         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1071         for (SequenceI seq : sg.getSequences())
1072         {
1073           jGroup.addSeq(seqHash(seq));
1074         }
1075       }
1076
1077       jms.setJGroup(groups);
1078     }
1079     if (!storeDS)
1080     {
1081       // /////////SAVE VIEWPORT
1082       Viewport view = new Viewport();
1083       view.setTitle(ap.alignFrame.getTitle());
1084       view.setSequenceSetId(makeHashCode(av.getSequenceSetId(),
1085               av.getSequenceSetId()));
1086       view.setId(av.getViewId());
1087       if (av.getCodingComplement() != null)
1088       {
1089         view.setComplementId(av.getCodingComplement().getViewId());
1090       }
1091       view.setViewName(av.viewName);
1092       view.setGatheredViews(av.isGatherViewsHere());
1093
1094       Rectangle position = ap.av.getExplodedGeometry();
1095       if (position == null)
1096       {
1097         position = ap.alignFrame.getBounds();
1098       }
1099       view.setXpos(position.x);
1100       view.setYpos(position.y);
1101       view.setWidth(position.width);
1102       view.setHeight(position.height);
1103
1104       view.setStartRes(av.startRes);
1105       view.setStartSeq(av.startSeq);
1106
1107       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1108       {
1109         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1110                 userColours, jms));
1111       }
1112       else if (av.getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
1113       {
1114         AnnotationColours ac = constructAnnotationColours(
1115                 (jalview.schemes.AnnotationColourGradient) av
1116                         .getGlobalColourScheme(),
1117                 userColours, jms);
1118
1119         view.setAnnotationColours(ac);
1120         view.setBgColour("AnnotationColourGradient");
1121       }
1122       else
1123       {
1124         view.setBgColour(ColourSchemeProperty.getColourName(av
1125                 .getGlobalColourScheme()));
1126       }
1127
1128       ColourSchemeI cs = av.getGlobalColourScheme();
1129
1130       if (cs != null)
1131       {
1132         if (cs.conservationApplied())
1133         {
1134           view.setConsThreshold(cs.getConservationInc());
1135           if (cs instanceof jalview.schemes.UserColourScheme)
1136           {
1137             view.setBgColour(setUserColourScheme(cs, userColours, jms));
1138           }
1139         }
1140
1141         if (cs instanceof ResidueColourScheme)
1142         {
1143           view.setPidThreshold(cs.getThreshold());
1144         }
1145       }
1146
1147       view.setConservationSelected(av.getConservationSelected());
1148       view.setPidSelected(av.getAbovePIDThreshold());
1149       view.setFontName(av.font.getName());
1150       view.setFontSize(av.font.getSize());
1151       view.setFontStyle(av.font.getStyle());
1152       view.setRenderGaps(av.isRenderGaps());
1153       view.setShowAnnotation(av.isShowAnnotation());
1154       view.setShowBoxes(av.getShowBoxes());
1155       view.setShowColourText(av.getColourText());
1156       view.setShowFullId(av.getShowJVSuffix());
1157       view.setRightAlignIds(av.isRightAlignIds());
1158       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
1159       view.setShowText(av.getShowText());
1160       view.setShowUnconserved(av.getShowUnconserved());
1161       view.setWrapAlignment(av.getWrapAlignment());
1162       view.setTextCol1(av.getTextColour().getRGB());
1163       view.setTextCol2(av.getTextColour2().getRGB());
1164       view.setTextColThreshold(av.getThresholdTextColour());
1165       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
1166       view.setShowSequenceLogo(av.isShowSequenceLogo());
1167       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
1168       view.setShowGroupConsensus(av.isShowGroupConsensus());
1169       view.setShowGroupConservation(av.isShowGroupConservation());
1170       view.setShowNPfeatureTooltip(av.isShowNPFeats());
1171       view.setShowDbRefTooltip(av.isShowDBRefs());
1172       view.setFollowHighlight(av.isFollowHighlight());
1173       view.setFollowSelection(av.followSelection);
1174       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
1175       if (av.getFeaturesDisplayed() != null)
1176       {
1177         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
1178
1179         String[] renderOrder = ap.getSeqPanel().seqCanvas
1180                 .getFeatureRenderer().getRenderOrder()
1181                 .toArray(new String[0]);
1182
1183         Vector settingsAdded = new Vector();
1184         Object gstyle = null;
1185         GraduatedColor gcol = null;
1186         if (renderOrder != null)
1187         {
1188           for (int ro = 0; ro < renderOrder.length; ro++)
1189           {
1190             gstyle = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1191                     .getFeatureStyle(renderOrder[ro]);
1192             Setting setting = new Setting();
1193             setting.setType(renderOrder[ro]);
1194             if (gstyle instanceof GraduatedColor)
1195             {
1196               gcol = (GraduatedColor) gstyle;
1197               setting.setColour(gcol.getMaxColor().getRGB());
1198               setting.setMincolour(gcol.getMinColor().getRGB());
1199               setting.setMin(gcol.getMin());
1200               setting.setMax(gcol.getMax());
1201               setting.setColourByLabel(gcol.isColourByLabel());
1202               setting.setAutoScale(gcol.isAutoScale());
1203               setting.setThreshold(gcol.getThresh());
1204               setting.setThreshstate(gcol.getThreshType());
1205             }
1206             else
1207             {
1208               setting.setColour(ap.getSeqPanel().seqCanvas
1209                       .getFeatureRenderer().getColour(renderOrder[ro])
1210                       .getRGB());
1211             }
1212
1213             setting.setDisplay(av.getFeaturesDisplayed().isVisible(
1214                     renderOrder[ro]));
1215             float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1216                     .getOrder(renderOrder[ro]);
1217             if (rorder > -1)
1218             {
1219               setting.setOrder(rorder);
1220             }
1221             fs.addSetting(setting);
1222             settingsAdded.addElement(renderOrder[ro]);
1223           }
1224         }
1225
1226         // Make sure we save none displayed feature settings
1227         Iterator en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1228                 .getFeatureColours().keySet().iterator();
1229         while (en.hasNext())
1230         {
1231           String key = en.next().toString();
1232           if (settingsAdded.contains(key))
1233           {
1234             continue;
1235           }
1236
1237           Setting setting = new Setting();
1238           setting.setType(key);
1239           setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1240                   .getColour(key).getRGB());
1241
1242           setting.setDisplay(false);
1243           float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1244                   .getOrder(key);
1245           if (rorder > -1)
1246           {
1247             setting.setOrder(rorder);
1248           }
1249           fs.addSetting(setting);
1250           settingsAdded.addElement(key);
1251         }
1252         // is groups actually supposed to be a map here ?
1253         en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1254                 .getFeatureGroups().iterator();
1255         Vector groupsAdded = new Vector();
1256         while (en.hasNext())
1257         {
1258           String grp = en.next().toString();
1259           if (groupsAdded.contains(grp))
1260           {
1261             continue;
1262           }
1263           Group g = new Group();
1264           g.setName(grp);
1265           g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
1266                   .getFeatureRenderer().checkGroupVisibility(grp, false))
1267                   .booleanValue());
1268           fs.addGroup(g);
1269           groupsAdded.addElement(grp);
1270         }
1271         jms.setFeatureSettings(fs);
1272
1273       }
1274
1275       if (av.hasHiddenColumns())
1276       {
1277         if (av.getColumnSelection() == null
1278                 || av.getColumnSelection().getHiddenColumns() == null)
1279         {
1280           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1281         }
1282         else
1283         {
1284           for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
1285                   .size(); c++)
1286           {
1287             int[] region = av.getColumnSelection().getHiddenColumns()
1288                     .get(c);
1289             HiddenColumns hc = new HiddenColumns();
1290             hc.setStart(region[0]);
1291             hc.setEnd(region[1]);
1292             view.addHiddenColumns(hc);
1293           }
1294         }
1295       }
1296       if (calcIdSet.size() > 0)
1297       {
1298         for (String calcId : calcIdSet)
1299         {
1300           if (calcId.trim().length() > 0)
1301           {
1302             CalcIdParam cidp = createCalcIdParam(calcId, av);
1303             // Some calcIds have no parameters.
1304             if (cidp != null)
1305             {
1306               view.addCalcIdParam(cidp);
1307             }
1308           }
1309         }
1310       }
1311
1312       jms.addViewport(view);
1313     }
1314     object.setJalviewModelSequence(jms);
1315     object.getVamsasModel().addSequenceSet(vamsasSet);
1316
1317     if (jout != null && fileName != null)
1318     {
1319       // We may not want to write the object to disk,
1320       // eg we can copy the alignViewport to a new view object
1321       // using save and then load
1322       try
1323       {
1324         System.out.println("Writing jar entry " + fileName);
1325         JarEntry entry = new JarEntry(fileName);
1326         jout.putNextEntry(entry);
1327         PrintWriter pout = new PrintWriter(new OutputStreamWriter(jout,
1328                 UTF_8));
1329         Marshaller marshaller = new Marshaller(pout);
1330         marshaller.marshal(object);
1331         pout.flush();
1332         jout.closeEntry();
1333       } catch (Exception ex)
1334       {
1335         // TODO: raise error in GUI if marshalling failed.
1336         ex.printStackTrace();
1337       }
1338     }
1339     return object;
1340   }
1341
1342   /**
1343    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1344    * for each viewer, with
1345    * <ul>
1346    * <li>viewer geometry (position, size, split pane divider location)</li>
1347    * <li>index of the selected structure in the viewer (currently shows gapped
1348    * or ungapped)</li>
1349    * <li>the id of the annotation holding RNA secondary structure</li>
1350    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1351    * </ul>
1352    * Varna viewer state is also written out (in native Varna XML) to separate
1353    * project jar entries. A separate entry is written for each RNA structure
1354    * displayed, with the naming convention
1355    * <ul>
1356    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1357    * </ul>
1358    * 
1359    * @param jout
1360    * @param jseq
1361    * @param jds
1362    * @param viewIds
1363    * @param ap
1364    * @param storeDataset
1365    */
1366   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1367           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1368           boolean storeDataset)
1369   {
1370     if (Desktop.desktop == null)
1371     {
1372       return;
1373     }
1374     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1375     for (int f = frames.length - 1; f > -1; f--)
1376     {
1377       if (frames[f] instanceof AppVarna)
1378       {
1379         AppVarna varna = (AppVarna) frames[f];
1380         /*
1381          * link the sequence to every viewer that is showing it and is linked to
1382          * its alignment panel
1383          */
1384         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1385         {
1386           String viewId = varna.getViewId();
1387           RnaViewer rna = new RnaViewer();
1388           rna.setViewId(viewId);
1389           rna.setTitle(varna.getTitle());
1390           rna.setXpos(varna.getX());
1391           rna.setYpos(varna.getY());
1392           rna.setWidth(varna.getWidth());
1393           rna.setHeight(varna.getHeight());
1394           rna.setDividerLocation(varna.getDividerLocation());
1395           rna.setSelectedRna(varna.getSelectedIndex());
1396           jseq.addRnaViewer(rna);
1397
1398           /*
1399            * Store each Varna panel's state once in the project per sequence.
1400            * First time through only (storeDataset==false)
1401            */
1402           // boolean storeSessions = false;
1403           // String sequenceViewId = viewId + seqsToIds.get(jds);
1404           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1405           // {
1406           // viewIds.add(sequenceViewId);
1407           // storeSessions = true;
1408           // }
1409           for (RnaModel model : varna.getModels())
1410           {
1411             if (model.seq == jds)
1412             {
1413               /*
1414                * VARNA saves each view (sequence or alignment secondary
1415                * structure, gapped or trimmed) as a separate XML file
1416                */
1417               String jarEntryName = rnaSessions.get(model);
1418               if (jarEntryName == null)
1419               {
1420
1421                 String varnaStateFile = varna.getStateInfo(model.rna);
1422                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1423                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1424                 rnaSessions.put(model, jarEntryName);
1425               }
1426               SecondaryStructure ss = new SecondaryStructure();
1427               String annotationId = varna.getAnnotation(jds).annotationId;
1428               ss.setAnnotationId(annotationId);
1429               ss.setViewerState(jarEntryName);
1430               ss.setGapped(model.gapped);
1431               ss.setTitle(model.title);
1432               rna.addSecondaryStructure(ss);
1433             }
1434           }
1435         }
1436       }
1437     }
1438   }
1439
1440   /**
1441    * Copy the contents of a file to a new entry added to the output jar
1442    * 
1443    * @param jout
1444    * @param infilePath
1445    * @param jarEntryName
1446    */
1447   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1448           String jarEntryName)
1449   {
1450     DataInputStream dis = null;
1451     try
1452     {
1453       File file = new File(infilePath);
1454       if (file.exists() && jout != null)
1455       {
1456         dis = new DataInputStream(new FileInputStream(file));
1457         byte[] data = new byte[(int) file.length()];
1458         dis.readFully(data);
1459         writeJarEntry(jout, jarEntryName, data);
1460       }
1461     } catch (Exception ex)
1462     {
1463       ex.printStackTrace();
1464     } finally
1465     {
1466       if (dis != null)
1467       {
1468         try
1469         {
1470           dis.close();
1471         } catch (IOException e)
1472         {
1473           // ignore
1474         }
1475       }
1476     }
1477   }
1478
1479   /**
1480    * Write the data to a new entry of given name in the output jar file
1481    * 
1482    * @param jout
1483    * @param jarEntryName
1484    * @param data
1485    * @throws IOException
1486    */
1487   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
1488           byte[] data) throws IOException
1489   {
1490     if (jout != null)
1491     {
1492       System.out.println("Writing jar entry " + jarEntryName);
1493       jout.putNextEntry(new JarEntry(jarEntryName));
1494       DataOutputStream dout = new DataOutputStream(jout);
1495       dout.write(data, 0, data.length);
1496       dout.flush();
1497       jout.closeEntry();
1498     }
1499   }
1500
1501   /**
1502    * Save the state of a structure viewer
1503    * 
1504    * @param ap
1505    * @param jds
1506    * @param pdb
1507    *          the archive XML element under which to save the state
1508    * @param entry
1509    * @param viewIds
1510    * @param matchedFile
1511    * @param viewFrame
1512    * @return
1513    */
1514   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
1515           Pdbids pdb, PDBEntry entry, List<String> viewIds,
1516           String matchedFile, StructureViewerBase viewFrame)
1517   {
1518     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
1519
1520     /*
1521      * Look for any bindings for this viewer to the PDB file of interest
1522      * (including part matches excluding chain id)
1523      */
1524     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
1525     {
1526       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
1527       final String pdbId = pdbentry.getId();
1528       if (!pdbId.equals(entry.getId())
1529               && !(entry.getId().length() > 4 && entry.getId()
1530                       .toLowerCase().startsWith(pdbId.toLowerCase())))
1531       {
1532         /*
1533          * not interested in a binding to a different PDB entry here
1534          */
1535         continue;
1536       }
1537       if (matchedFile == null)
1538       {
1539         matchedFile = pdbentry.getFile();
1540       }
1541       else if (!matchedFile.equals(pdbentry.getFile()))
1542       {
1543         Cache.log
1544                 .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
1545                         + pdbentry.getFile());
1546       }
1547       // record the
1548       // file so we
1549       // can get at it if the ID
1550       // match is ambiguous (e.g.
1551       // 1QIP==1qipA)
1552
1553       for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
1554       {
1555         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
1556         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
1557         {
1558           StructureState state = new StructureState();
1559           state.setVisible(true);
1560           state.setXpos(viewFrame.getX());
1561           state.setYpos(viewFrame.getY());
1562           state.setWidth(viewFrame.getWidth());
1563           state.setHeight(viewFrame.getHeight());
1564           final String viewId = viewFrame.getViewId();
1565           state.setViewId(viewId);
1566           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
1567           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
1568           state.setColourByJmol(viewFrame.isColouredByViewer());
1569           state.setType(viewFrame.getViewerType().toString());
1570           pdb.addStructureState(state);
1571         }
1572       }
1573     }
1574     return matchedFile;
1575   }
1576
1577   private AnnotationColours constructAnnotationColours(
1578           AnnotationColourGradient acg, List<UserColourScheme> userColours,
1579           JalviewModelSequence jms)
1580   {
1581     AnnotationColours ac = new AnnotationColours();
1582     ac.setAboveThreshold(acg.getAboveThreshold());
1583     ac.setThreshold(acg.getAnnotationThreshold());
1584     ac.setAnnotation(acg.getAnnotation());
1585     if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
1586     {
1587       ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
1588               userColours, jms));
1589     }
1590     else
1591     {
1592       ac.setColourScheme(ColourSchemeProperty.getColourName(acg
1593               .getBaseColour()));
1594     }
1595
1596     ac.setMaxColour(acg.getMaxColour().getRGB());
1597     ac.setMinColour(acg.getMinColour().getRGB());
1598     ac.setPerSequence(acg.isSeqAssociated());
1599     ac.setPredefinedColours(acg.isPredefinedColours());
1600     return ac;
1601   }
1602
1603   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
1604           IdentityHashMap<SequenceGroup, String> groupRefs,
1605           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
1606           SequenceSet vamsasSet)
1607   {
1608
1609     for (int i = 0; i < aa.length; i++)
1610     {
1611       Annotation an = new Annotation();
1612
1613       AlignmentAnnotation annotation = aa[i];
1614       if (annotation.annotationId != null)
1615       {
1616         annotationIds.put(annotation.annotationId, annotation);
1617       }
1618
1619       an.setId(annotation.annotationId);
1620
1621       an.setVisible(annotation.visible);
1622
1623       an.setDescription(annotation.description);
1624
1625       if (annotation.sequenceRef != null)
1626       {
1627         // 2.9 JAL-1781 xref on sequence id rather than name
1628         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
1629       }
1630       if (annotation.groupRef != null)
1631       {
1632         String groupIdr = groupRefs.get(annotation.groupRef);
1633         if (groupIdr == null)
1634         {
1635           // make a locally unique String
1636           groupRefs.put(
1637                   annotation.groupRef,
1638                   groupIdr = ("" + System.currentTimeMillis()
1639                           + annotation.groupRef.getName() + groupRefs
1640                           .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       {
2420         Viewport view = candidate.getKey();
2421         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2422                 view.getHeight());
2423         System.err.println("Failed to restore view " + view.getTitle()
2424                 + " to split frame");
2425       }
2426     }
2427
2428     /*
2429      * Gather back into tabbed views as flagged.
2430      */
2431     for (SplitFrame sf : gatherTo)
2432     {
2433       Desktop.instance.gatherViews(sf);
2434     }
2435
2436     splitFrameCandidates.clear();
2437   }
2438
2439   /**
2440    * Construct and display one SplitFrame holding DNA and protein alignments.
2441    * 
2442    * @param dnaFrame
2443    * @param proteinFrame
2444    * @return
2445    */
2446   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2447           AlignFrame proteinFrame)
2448   {
2449     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2450     String title = MessageManager.getString("label.linked_view_title");
2451     int width = (int) dnaFrame.getBounds().getWidth();
2452     int height = (int) (dnaFrame.getBounds().getHeight()
2453             + proteinFrame.getBounds().getHeight() + 50);
2454     Desktop.addInternalFrame(splitFrame, title, width, height);
2455
2456     /*
2457      * And compute cDNA consensus (couldn't do earlier with consensus as
2458      * mappings were not yet present)
2459      */
2460     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2461
2462     return splitFrame;
2463   }
2464
2465   /**
2466    * check errorMessage for a valid error message and raise an error box in the
2467    * GUI or write the current errorMessage to stderr and then clear the error
2468    * state.
2469    */
2470   protected void reportErrors()
2471   {
2472     reportErrors(false);
2473   }
2474
2475   protected void reportErrors(final boolean saving)
2476   {
2477     if (errorMessage != null)
2478     {
2479       final String finalErrorMessage = errorMessage;
2480       if (raiseGUI)
2481       {
2482         javax.swing.SwingUtilities.invokeLater(new Runnable()
2483         {
2484           @Override
2485           public void run()
2486           {
2487             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2488                     finalErrorMessage, "Error "
2489                             + (saving ? "saving" : "loading")
2490                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2491           }
2492         });
2493       }
2494       else
2495       {
2496         System.err.println("Problem loading Jalview file: " + errorMessage);
2497       }
2498     }
2499     errorMessage = null;
2500   }
2501
2502   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2503
2504   /**
2505    * when set, local views will be updated from view stored in JalviewXML
2506    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2507    * sync if this is set to true.
2508    */
2509   private final boolean updateLocalViews = false;
2510
2511   /**
2512    * Returns the path to a temporary file holding the PDB file for the given PDB
2513    * id. The first time of asking, searches for a file of that name in the
2514    * Jalview project jar, and copies it to a new temporary file. Any repeat
2515    * requests just return the path to the file previously created.
2516    * 
2517    * @param jprovider
2518    * @param pdbId
2519    * @return
2520    */
2521   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
2522   {
2523     if (alreadyLoadedPDB.containsKey(pdbId))
2524     {
2525       return alreadyLoadedPDB.get(pdbId).toString();
2526     }
2527
2528     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
2529     if (tempFile != null)
2530     {
2531       alreadyLoadedPDB.put(pdbId, tempFile);
2532     }
2533     return tempFile;
2534   }
2535
2536   /**
2537    * Copies the jar entry of given name to a new temporary file and returns the
2538    * path to the file, or null if the entry is not found.
2539    * 
2540    * @param jprovider
2541    * @param jarEntryName
2542    * @param prefix
2543    *          a prefix for the temporary file name, must be at least three
2544    *          characters long
2545    * @return
2546    */
2547   protected String copyJarEntry(jarInputStreamProvider jprovider,
2548           String jarEntryName, String prefix)
2549   {
2550     BufferedReader in = null;
2551     PrintWriter out = null;
2552
2553     try
2554     {
2555       JarInputStream jin = jprovider.getJarInputStream();
2556       /*
2557        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2558        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2559        * FileInputStream(jprovider)); }
2560        */
2561
2562       JarEntry entry = null;
2563       do
2564       {
2565         entry = jin.getNextJarEntry();
2566       } while (entry != null && !entry.getName().equals(jarEntryName));
2567       if (entry != null)
2568       {
2569         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2570         File outFile = File.createTempFile(prefix, ".tmp");
2571         outFile.deleteOnExit();
2572         out = new PrintWriter(new FileOutputStream(outFile));
2573         String data;
2574
2575         while ((data = in.readLine()) != null)
2576         {
2577           out.println(data);
2578         }
2579         out.flush();
2580         String t = outFile.getAbsolutePath();
2581         return t;
2582       }
2583       else
2584       {
2585         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2586       }
2587     } catch (Exception ex)
2588     {
2589       ex.printStackTrace();
2590     } finally
2591     {
2592       if (in != null)
2593       {
2594         try
2595         {
2596           in.close();
2597         } catch (IOException e)
2598         {
2599           // ignore
2600         }
2601       }
2602       if (out != null)
2603       {
2604         out.close();
2605       }
2606     }
2607
2608     return null;
2609   }
2610
2611   private class JvAnnotRow
2612   {
2613     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2614     {
2615       order = i;
2616       template = jaa;
2617     }
2618
2619     /**
2620      * persisted version of annotation row from which to take vis properties
2621      */
2622     public jalview.datamodel.AlignmentAnnotation template;
2623
2624     /**
2625      * original position of the annotation row in the alignment
2626      */
2627     public int order;
2628   }
2629
2630   /**
2631    * Load alignment frame from jalview XML DOM object
2632    * 
2633    * @param object
2634    *          DOM
2635    * @param file
2636    *          filename source string
2637    * @param loadTreesAndStructures
2638    *          when false only create Viewport
2639    * @param jprovider
2640    *          data source provider
2641    * @return alignment frame created from view stored in DOM
2642    */
2643   AlignFrame loadFromObject(JalviewModel object, String file,
2644           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2645   {
2646     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2647     Sequence[] vamsasSeq = vamsasSet.getSequence();
2648
2649     JalviewModelSequence jms = object.getJalviewModelSequence();
2650
2651     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2652             : null;
2653
2654     // ////////////////////////////////
2655     // LOAD SEQUENCES
2656
2657     List<SequenceI> hiddenSeqs = null;
2658     jalview.datamodel.Sequence jseq;
2659
2660     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2661
2662     boolean multipleView = false;
2663
2664     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2665     int vi = 0; // counter in vamsasSeq array
2666     for (int i = 0; i < jseqs.length; i++)
2667     {
2668       String seqId = jseqs[i].getId();
2669
2670       if (seqRefIds.get(seqId) != null)
2671       {
2672         tmpseqs.add(seqRefIds.get(seqId));
2673         multipleView = true;
2674       }
2675       else
2676       {
2677         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2678                 vamsasSeq[vi].getSequence());
2679         jseq.setDescription(vamsasSeq[vi].getDescription());
2680         jseq.setStart(jseqs[i].getStart());
2681         jseq.setEnd(jseqs[i].getEnd());
2682         jseq.setVamsasId(uniqueSetSuffix + seqId);
2683         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
2684         tmpseqs.add(jseq);
2685         vi++;
2686       }
2687
2688       if (jseqs[i].getHidden())
2689       {
2690         if (hiddenSeqs == null)
2691         {
2692           hiddenSeqs = new ArrayList<SequenceI>();
2693         }
2694
2695         hiddenSeqs.add(seqRefIds.get(seqId));
2696       }
2697
2698     }
2699
2700     // /
2701     // Create the alignment object from the sequence set
2702     // ///////////////////////////////
2703     SequenceI[] orderedSeqs = tmpseqs
2704             .toArray(new SequenceI[tmpseqs.size()]);
2705
2706     Alignment al = new Alignment(orderedSeqs);
2707
2708     // / Add the alignment properties
2709     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2710     {
2711       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2712       al.setProperty(ssp.getKey(), ssp.getValue());
2713     }
2714
2715     // /
2716     // SequenceFeatures are added to the DatasetSequence,
2717     // so we must create or recover the dataset before loading features
2718     // ///////////////////////////////
2719     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2720     {
2721       // older jalview projects do not have a dataset id.
2722       al.setDataset(null);
2723     }
2724     else
2725     {
2726       // recover dataset - passing on flag indicating if this a 'viewless'
2727       // sequence set (a.k.a. a stored dataset for the project)
2728       recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
2729               .getViewportCount() == 0);
2730     }
2731     // ///////////////////////////////
2732
2733     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2734     if (!multipleView)
2735     {
2736       // load sequence features, database references and any associated PDB
2737       // structures for the alignment
2738       for (int i = 0; i < vamsasSeq.length; i++)
2739       {
2740         if (jseqs[i].getFeaturesCount() > 0)
2741         {
2742           Features[] features = jseqs[i].getFeatures();
2743           for (int f = 0; f < features.length; f++)
2744           {
2745             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2746                     features[f].getType(), features[f].getDescription(),
2747                     features[f].getStatus(), features[f].getBegin(),
2748                     features[f].getEnd(), features[f].getFeatureGroup());
2749
2750             sf.setScore(features[f].getScore());
2751             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2752             {
2753               OtherData keyValue = features[f].getOtherData(od);
2754               if (keyValue.getKey().startsWith("LINK"))
2755               {
2756                 sf.addLink(keyValue.getValue());
2757               }
2758               else
2759               {
2760                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2761               }
2762
2763             }
2764
2765             al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
2766           }
2767         }
2768         if (vamsasSeq[i].getDBRefCount() > 0)
2769         {
2770           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
2771         }
2772         if (jseqs[i].getPdbidsCount() > 0)
2773         {
2774           Pdbids[] ids = jseqs[i].getPdbids();
2775           for (int p = 0; p < ids.length; p++)
2776           {
2777             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2778             entry.setId(ids[p].getId());
2779             if (ids[p].getType() != null)
2780             {
2781               if (ids[p].getType().equalsIgnoreCase("PDB"))
2782               {
2783                 entry.setType(PDBEntry.Type.PDB);
2784               }
2785               else
2786               {
2787                 entry.setType(PDBEntry.Type.FILE);
2788               }
2789             }
2790             if (ids[p].getFile() != null)
2791             {
2792               if (!pdbloaded.containsKey(ids[p].getFile()))
2793               {
2794                 entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
2795               }
2796               else
2797               {
2798                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
2799               }
2800             }
2801             StructureSelectionManager.getStructureSelectionManager(
2802                     Desktop.instance).registerPDBEntry(entry);
2803             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
2804           }
2805         }
2806       }
2807     } // end !multipleview
2808
2809     // ///////////////////////////////
2810     // LOAD SEQUENCE MAPPINGS
2811
2812     if (vamsasSet.getAlcodonFrameCount() > 0)
2813     {
2814       // TODO Potentially this should only be done once for all views of an
2815       // alignment
2816       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
2817       for (int i = 0; i < alc.length; i++)
2818       {
2819         AlignedCodonFrame cf = new AlignedCodonFrame();
2820         if (alc[i].getAlcodMapCount() > 0)
2821         {
2822           AlcodMap[] maps = alc[i].getAlcodMap();
2823           for (int m = 0; m < maps.length; m++)
2824           {
2825             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
2826             // Load Mapping
2827             jalview.datamodel.Mapping mapping = null;
2828             // attach to dna sequence reference.
2829             if (maps[m].getMapping() != null)
2830             {
2831               mapping = addMapping(maps[m].getMapping());
2832             }
2833             if (dnaseq != null)
2834             {
2835               cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
2836             }
2837             else
2838             {
2839               // defer to later
2840               frefedSequence.add(new Object[]
2841               { maps[m].getDnasq(), cf, mapping });
2842             }
2843           }
2844         }
2845         al.addCodonFrame(cf);
2846       }
2847     }
2848
2849     // ////////////////////////////////
2850     // LOAD ANNOTATIONS
2851     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
2852
2853     /*
2854      * store any annotations which forward reference a group's ID
2855      */
2856     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
2857
2858     if (vamsasSet.getAnnotationCount() > 0)
2859     {
2860       Annotation[] an = vamsasSet.getAnnotation();
2861
2862       for (int i = 0; i < an.length; i++)
2863       {
2864         Annotation annotation = an[i];
2865
2866         /**
2867          * test if annotation is automatically calculated for this view only
2868          */
2869         boolean autoForView = false;
2870         if (annotation.getLabel().equals("Quality")
2871                 || annotation.getLabel().equals("Conservation")
2872                 || annotation.getLabel().equals("Consensus"))
2873         {
2874           // Kludge for pre 2.5 projects which lacked the autocalculated flag
2875           autoForView = true;
2876           if (!annotation.hasAutoCalculated())
2877           {
2878             annotation.setAutoCalculated(true);
2879           }
2880         }
2881         if (autoForView
2882                 || (annotation.hasAutoCalculated() && annotation
2883                         .isAutoCalculated()))
2884         {
2885           // remove ID - we don't recover annotation from other views for
2886           // view-specific annotation
2887           annotation.setId(null);
2888         }
2889
2890         // set visiblity for other annotation in this view
2891         String annotationId = annotation.getId();
2892         if (annotationId != null && 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(), jGroup.getEnd());
3127
3128         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3129
3130         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3131         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3132         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3133                 .isShowUnconserved() : false);
3134         sg.thresholdTextColour = jGroup.getTextColThreshold();
3135         if (jGroup.hasShowConsensusHistogram())
3136         {
3137           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3138         }
3139         ;
3140         if (jGroup.hasShowSequenceLogo())
3141         {
3142           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3143         }
3144         if (jGroup.hasNormaliseSequenceLogo())
3145         {
3146           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3147         }
3148         if (jGroup.hasIgnoreGapsinConsensus())
3149         {
3150           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3151         }
3152         if (jGroup.getConsThreshold() != 0)
3153         {
3154           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
3155                   "All", ResidueProperties.propHash, 3,
3156                   sg.getSequences(null), 0, sg.getWidth() - 1);
3157           c.calculate();
3158           c.verdict(false, 25);
3159           sg.cs.setConservation(c);
3160         }
3161
3162         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3163         {
3164           // re-instate unique group/annotation row reference
3165           List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
3166                   .getId());
3167           if (jaal != null)
3168           {
3169             for (AlignmentAnnotation jaa : jaal)
3170             {
3171               jaa.groupRef = sg;
3172               if (jaa.autoCalculated)
3173               {
3174                 // match up and try to set group autocalc alignment row for this
3175                 // annotation
3176                 if (jaa.label.startsWith("Consensus for "))
3177                 {
3178                   sg.setConsensus(jaa);
3179                 }
3180                 // match up and try to set group autocalc alignment row for this
3181                 // annotation
3182                 if (jaa.label.startsWith("Conservation for "))
3183                 {
3184                   sg.setConservationRow(jaa);
3185                 }
3186               }
3187             }
3188           }
3189         }
3190         al.addGroup(sg);
3191         if (addAnnotSchemeGroup)
3192         {
3193           // reconstruct the annotation colourscheme
3194           sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(),
3195                   null, al, jms, false);
3196         }
3197       }
3198     }
3199     if (view == null)
3200     {
3201       // only dataset in this model, so just return.
3202       return null;
3203     }
3204     // ///////////////////////////////
3205     // LOAD VIEWPORT
3206
3207     // If we just load in the same jar file again, the sequenceSetId
3208     // will be the same, and we end up with multiple references
3209     // to the same sequenceSet. We must modify this id on load
3210     // so that each load of the file gives a unique id
3211     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3212     String viewId = (view.getId() == null ? null : view.getId()
3213             + uniqueSetSuffix);
3214     AlignFrame af = null;
3215     AlignViewport av = null;
3216     // now check to see if we really need to create a new viewport.
3217     if (multipleView && viewportsAdded.size() == 0)
3218     {
3219       // We recovered an alignment for which a viewport already exists.
3220       // TODO: fix up any settings necessary for overlaying stored state onto
3221       // state recovered from another document. (may not be necessary).
3222       // we may need a binding from a viewport in memory to one recovered from
3223       // XML.
3224       // and then recover its containing af to allow the settings to be applied.
3225       // TODO: fix for vamsas demo
3226       System.err
3227               .println("About to recover a viewport for existing alignment: Sequence set ID is "
3228                       + uniqueSeqSetId);
3229       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3230       if (seqsetobj != null)
3231       {
3232         if (seqsetobj instanceof String)
3233         {
3234           uniqueSeqSetId = (String) seqsetobj;
3235           System.err
3236                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3237                           + uniqueSeqSetId);
3238         }
3239         else
3240         {
3241           System.err
3242                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
3243         }
3244
3245       }
3246     }
3247     /**
3248      * indicate that annotation colours are applied across all groups (pre
3249      * Jalview 2.8.1 behaviour)
3250      */
3251     boolean doGroupAnnColour = isVersionStringLaterThan("2.8.1",
3252             object.getVersion());
3253
3254     AlignmentPanel ap = null;
3255     boolean isnewview = true;
3256     if (viewId != null)
3257     {
3258       // Check to see if this alignment already has a view id == viewId
3259       jalview.gui.AlignmentPanel views[] = Desktop
3260               .getAlignmentPanels(uniqueSeqSetId);
3261       if (views != null && views.length > 0)
3262       {
3263         for (int v = 0; v < views.length; v++)
3264         {
3265           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3266           {
3267             // recover the existing alignpanel, alignframe, viewport
3268             af = views[v].alignFrame;
3269             av = views[v].av;
3270             ap = views[v];
3271             // TODO: could even skip resetting view settings if we don't want to
3272             // change the local settings from other jalview processes
3273             isnewview = false;
3274           }
3275         }
3276       }
3277     }
3278
3279     if (isnewview)
3280     {
3281       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3282               uniqueSeqSetId, viewId, autoAlan);
3283       av = af.viewport;
3284       ap = af.alignPanel;
3285     }
3286
3287     /*
3288      * Load any trees, PDB structures and viewers
3289      * 
3290      * Not done if flag is false (when this method is used for New View)
3291      */
3292     if (loadTreesAndStructures)
3293     {
3294       loadTrees(jms, view, af, av, ap);
3295       loadPDBStructures(jprovider, jseqs, af, ap);
3296       loadRnaViewers(jprovider, jseqs, ap);
3297     }
3298     // and finally return.
3299     return af;
3300   }
3301
3302   /**
3303    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3304    * panel is restored from separate jar entries, two (gapped and trimmed) per
3305    * sequence and secondary structure.
3306    * 
3307    * Currently each viewer shows just one sequence and structure (gapped and
3308    * trimmed), however this method is designed to support multiple sequences or
3309    * structures in viewers if wanted in future.
3310    * 
3311    * @param jprovider
3312    * @param jseqs
3313    * @param ap
3314    */
3315   private void loadRnaViewers(jarInputStreamProvider jprovider,
3316           JSeq[] jseqs, AlignmentPanel ap)
3317   {
3318     /*
3319      * scan the sequences for references to viewers; create each one the first
3320      * time it is referenced, add Rna models to existing viewers
3321      */
3322     for (JSeq jseq : jseqs)
3323     {
3324       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3325       {
3326         RnaViewer viewer = jseq.getRnaViewer(i);
3327         AppVarna appVarna = findOrCreateVarnaViewer(viewer,
3328                 uniqueSetSuffix, ap);
3329
3330         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3331         {
3332           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3333           SequenceI seq = seqRefIds.get(jseq.getId());
3334           AlignmentAnnotation ann = this.annotationIds.get(ss
3335                   .getAnnotationId());
3336
3337           /*
3338            * add the structure to the Varna display (with session state copied
3339            * from the jar to a temporary file)
3340            */
3341           boolean gapped = ss.isGapped();
3342           String rnaTitle = ss.getTitle();
3343           String sessionState = ss.getViewerState();
3344           String tempStateFile = copyJarEntry(jprovider, sessionState,
3345                   "varna");
3346           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3347           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3348         }
3349         appVarna.setInitialSelection(viewer.getSelectedRna());
3350       }
3351     }
3352   }
3353
3354   /**
3355    * Locate and return an already instantiated matching AppVarna, or create one
3356    * if not found
3357    * 
3358    * @param viewer
3359    * @param viewIdSuffix
3360    * @param ap
3361    * @return
3362    */
3363   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3364           String viewIdSuffix, AlignmentPanel ap)
3365   {
3366     /*
3367      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3368      * if load is repeated
3369      */
3370     String postLoadId = viewer.getViewId() + viewIdSuffix;
3371     for (JInternalFrame frame : getAllFrames())
3372     {
3373       if (frame instanceof AppVarna)
3374       {
3375         AppVarna varna = (AppVarna) frame;
3376         if (postLoadId.equals(varna.getViewId()))
3377         {
3378           // this viewer is already instantiated
3379           // could in future here add ap as another 'parent' of the
3380           // AppVarna window; currently just 1-to-many
3381           return varna;
3382         }
3383       }
3384     }
3385
3386     /*
3387      * viewer not found - make it
3388      */
3389     RnaViewerModel model = new RnaViewerModel(postLoadId,
3390             viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
3391             viewer.getWidth(), viewer.getHeight(),
3392             viewer.getDividerLocation());
3393     AppVarna varna = new AppVarna(model, ap);
3394
3395     return varna;
3396   }
3397
3398   /**
3399    * Load any saved trees
3400    * 
3401    * @param jms
3402    * @param view
3403    * @param af
3404    * @param av
3405    * @param ap
3406    */
3407   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3408           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3409   {
3410     // TODO result of automated refactoring - are all these parameters needed?
3411     try
3412     {
3413       for (int t = 0; t < jms.getTreeCount(); t++)
3414       {
3415
3416         Tree tree = jms.getTree(t);
3417
3418         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3419         if (tp == null)
3420         {
3421           tp = af.ShowNewickTree(
3422                   new jalview.io.NewickFile(tree.getNewick()),
3423                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3424                   tree.getXpos(), tree.getYpos());
3425           if (tree.getId() != null)
3426           {
3427             // perhaps bind the tree id to something ?
3428           }
3429         }
3430         else
3431         {
3432           // update local tree attributes ?
3433           // TODO: should check if tp has been manipulated by user - if so its
3434           // settings shouldn't be modified
3435           tp.setTitle(tree.getTitle());
3436           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3437                   .getWidth(), tree.getHeight()));
3438           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3439           // views'
3440           // works still
3441           tp.treeCanvas.av = av; // af.viewport;
3442           tp.treeCanvas.ap = ap; // af.alignPanel;
3443
3444         }
3445         if (tp == null)
3446         {
3447           warn("There was a problem recovering stored Newick tree: \n"
3448                   + tree.getNewick());
3449           continue;
3450         }
3451
3452         tp.fitToWindow.setState(tree.getFitToWindow());
3453         tp.fitToWindow_actionPerformed(null);
3454
3455         if (tree.getFontName() != null)
3456         {
3457           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3458                   .getFontStyle(), tree.getFontSize()));
3459         }
3460         else
3461         {
3462           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3463                   .getFontStyle(), tree.getFontSize()));
3464         }
3465
3466         tp.showPlaceholders(tree.getMarkUnlinked());
3467         tp.showBootstrap(tree.getShowBootstrap());
3468         tp.showDistances(tree.getShowDistances());
3469
3470         tp.treeCanvas.threshold = tree.getThreshold();
3471
3472         if (tree.getCurrentTree())
3473         {
3474           af.viewport.setCurrentTree(tp.getTree());
3475         }
3476       }
3477
3478     } catch (Exception ex)
3479     {
3480       ex.printStackTrace();
3481     }
3482   }
3483
3484   /**
3485    * Load and link any saved structure viewers.
3486    * 
3487    * @param jprovider
3488    * @param jseqs
3489    * @param af
3490    * @param ap
3491    */
3492   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3493           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3494   {
3495     /*
3496      * Run through all PDB ids on the alignment, and collect mappings between
3497      * distinct view ids and all sequences referring to that view.
3498      */
3499     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3500
3501     for (int i = 0; i < jseqs.length; i++)
3502     {
3503       if (jseqs[i].getPdbidsCount() > 0)
3504       {
3505         Pdbids[] ids = jseqs[i].getPdbids();
3506         for (int p = 0; p < ids.length; p++)
3507         {
3508           final int structureStateCount = ids[p].getStructureStateCount();
3509           for (int s = 0; s < structureStateCount; s++)
3510           {
3511             // check to see if we haven't already created this structure view
3512             final StructureState structureState = ids[p]
3513                     .getStructureState(s);
3514             String sviewid = (structureState.getViewId() == null) ? null
3515                     : structureState.getViewId() + uniqueSetSuffix;
3516             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3517             // Originally : ids[p].getFile()
3518             // : TODO: verify external PDB file recovery still works in normal
3519             // jalview project load
3520             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
3521             jpdb.setId(ids[p].getId());
3522
3523             int x = structureState.getXpos();
3524             int y = structureState.getYpos();
3525             int width = structureState.getWidth();
3526             int height = structureState.getHeight();
3527
3528             // Probably don't need to do this anymore...
3529             // Desktop.desktop.getComponentAt(x, y);
3530             // TODO: NOW: check that this recovers the PDB file correctly.
3531             String pdbFile = loadPDBFile(jprovider, ids[p].getId());
3532             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3533                     .getId() + "");
3534             if (sviewid == null)
3535             {
3536               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3537                       + "," + height;
3538             }
3539             if (!structureViewers.containsKey(sviewid))
3540             {
3541               structureViewers.put(sviewid,
3542                       new StructureViewerModel(x, y, width, height, false,
3543                               false, true, structureState.getViewId(),
3544                               structureState.getType()));
3545               // Legacy pre-2.7 conversion JAL-823 :
3546               // do not assume any view has to be linked for colour by
3547               // sequence
3548             }
3549
3550             // assemble String[] { pdb files }, String[] { id for each
3551             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3552             // seqs_file 2}, boolean[] {
3553             // linkAlignPanel,superposeWithAlignpanel}} from hash
3554             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3555             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3556                     | (structureState.hasAlignwithAlignPanel() ? structureState
3557                             .getAlignwithAlignPanel() : false));
3558
3559             /*
3560              * Default colour by linked panel to false if not specified (e.g.
3561              * for pre-2.7 projects)
3562              */
3563             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3564             colourWithAlignPanel |= (structureState
3565                     .hasColourwithAlignPanel() ? structureState
3566                     .getColourwithAlignPanel() : false);
3567             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3568
3569             /*
3570              * Default colour by viewer to true if not specified (e.g. for
3571              * pre-2.7 projects)
3572              */
3573             boolean colourByViewer = jmoldat.isColourByViewer();
3574             colourByViewer &= structureState.hasColourByJmol() ? structureState
3575                     .getColourByJmol() : true;
3576             jmoldat.setColourByViewer(colourByViewer);
3577
3578             if (jmoldat.getStateData().length() < structureState
3579                     .getContent().length())
3580             {
3581               {
3582                 jmoldat.setStateData(structureState.getContent());
3583               }
3584             }
3585             if (ids[p].getFile() != null)
3586             {
3587               File mapkey = new File(ids[p].getFile());
3588               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3589               if (seqstrmaps == null)
3590               {
3591                 jmoldat.getFileData().put(
3592                         mapkey,
3593                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3594                                 ids[p].getId()));
3595               }
3596               if (!seqstrmaps.getSeqList().contains(seq))
3597               {
3598                 seqstrmaps.getSeqList().add(seq);
3599                 // TODO and chains?
3600               }
3601             }
3602             else
3603             {
3604               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");
3605               warn(errorMessage);
3606             }
3607           }
3608         }
3609       }
3610     }
3611     // Instantiate the associated structure views
3612     for (Entry<String, StructureViewerModel> entry : structureViewers
3613             .entrySet())
3614     {
3615       try
3616       {
3617         createOrLinkStructureViewer(entry, af, ap, jprovider);
3618       } catch (Exception e)
3619       {
3620         System.err.println("Error loading structure viewer: "
3621                 + e.getMessage());
3622         // failed - try the next one
3623       }
3624     }
3625   }
3626
3627   /**
3628    * 
3629    * @param viewerData
3630    * @param af
3631    * @param ap
3632    * @param jprovider
3633    */
3634   protected void createOrLinkStructureViewer(
3635           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3636           AlignmentPanel ap, jarInputStreamProvider jprovider)
3637   {
3638     final StructureViewerModel stateData = viewerData.getValue();
3639
3640     /*
3641      * Search for any viewer windows already open from other alignment views
3642      * that exactly match the stored structure state
3643      */
3644     StructureViewerBase comp = findMatchingViewer(viewerData);
3645
3646     if (comp != null)
3647     {
3648       linkStructureViewer(ap, comp, stateData);
3649       return;
3650     }
3651
3652     /*
3653      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3654      * "viewer_"+stateData.viewId
3655      */
3656     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3657     {
3658       createChimeraViewer(viewerData, af, jprovider);
3659     }
3660     else
3661     {
3662       /*
3663        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3664        */
3665       createJmolViewer(viewerData, af, jprovider);
3666     }
3667   }
3668
3669   /**
3670    * Create a new Chimera viewer.
3671    * 
3672    * @param data
3673    * @param af
3674    * @param jprovider
3675    */
3676   protected void createChimeraViewer(
3677           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3678           jarInputStreamProvider jprovider)
3679   {
3680     StructureViewerModel data = viewerData.getValue();
3681     String chimeraSessionFile = data.getStateData();
3682
3683     /*
3684      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3685      * 
3686      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3687      * 'uniquified' sviewid used to reconstruct the viewer here
3688      */
3689     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3690     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3691             "chimera");
3692
3693     Set<Entry<File, StructureData>> fileData = data.getFileData()
3694             .entrySet();
3695     List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3696     List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3697     for (Entry<File, StructureData> pdb : fileData)
3698     {
3699       String filePath = pdb.getValue().getFilePath();
3700       String pdbId = pdb.getValue().getPdbId();
3701       // pdbs.add(new PDBEntry(filePath, pdbId));
3702       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3703       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3704       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3705       allseqs.add(seqs);
3706     }
3707
3708     boolean colourByChimera = data.isColourByViewer();
3709     boolean colourBySequence = data.isColourWithAlignPanel();
3710
3711     // TODO use StructureViewer as a factory here, see JAL-1761
3712     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3713     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3714             .size()][]);
3715     String newViewId = viewerData.getKey();
3716
3717     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3718             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3719             colourBySequence, newViewId);
3720     cvf.setSize(data.getWidth(), data.getHeight());
3721     cvf.setLocation(data.getX(), data.getY());
3722   }
3723
3724   /**
3725    * Create a new Jmol window. First parse the Jmol state to translate filenames
3726    * loaded into the view, and record the order in which files are shown in the
3727    * Jmol view, so we can add the sequence mappings in same order.
3728    * 
3729    * @param viewerData
3730    * @param af
3731    * @param jprovider
3732    */
3733   protected void createJmolViewer(
3734           final Entry<String, StructureViewerModel> viewerData,
3735           AlignFrame af, jarInputStreamProvider jprovider)
3736   {
3737     final StructureViewerModel svattrib = viewerData.getValue();
3738     String state = svattrib.getStateData();
3739
3740     /*
3741      * Pre-2.9: state element value is the Jmol state string
3742      * 
3743      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3744      * + viewId
3745      */
3746     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3747     {
3748       state = readJarEntry(jprovider,
3749               getViewerJarEntryName(svattrib.getViewId()));
3750     }
3751
3752     List<String> pdbfilenames = new ArrayList<String>();
3753     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3754     List<String> pdbids = new ArrayList<String>();
3755     StringBuilder newFileLoc = new StringBuilder(64);
3756     int cp = 0, ncp, ecp;
3757     Map<File, StructureData> oldFiles = svattrib.getFileData();
3758     while ((ncp = state.indexOf("load ", cp)) > -1)
3759     {
3760       do
3761       {
3762         // look for next filename in load statement
3763         newFileLoc.append(state.substring(cp,
3764                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3765         String oldfilenam = state.substring(ncp,
3766                 ecp = state.indexOf("\"", ncp));
3767         // recover the new mapping data for this old filename
3768         // have to normalize filename - since Jmol and jalview do
3769         // filename
3770         // translation differently.
3771         StructureData filedat = oldFiles.get(new File(oldfilenam));
3772         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3773         pdbfilenames.add(filedat.getFilePath());
3774         pdbids.add(filedat.getPdbId());
3775         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3776         newFileLoc.append("\"");
3777         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3778                       // look for next file statement.
3779       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3780     }
3781     if (cp > 0)
3782     {
3783       // just append rest of state
3784       newFileLoc.append(state.substring(cp));
3785     }
3786     else
3787     {
3788       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3789       newFileLoc = new StringBuilder(state);
3790       newFileLoc.append("; load append ");
3791       for (File id : oldFiles.keySet())
3792       {
3793         // add this and any other pdb files that should be present in
3794         // the viewer
3795         StructureData filedat = oldFiles.get(id);
3796         newFileLoc.append(filedat.getFilePath());
3797         pdbfilenames.add(filedat.getFilePath());
3798         pdbids.add(filedat.getPdbId());
3799         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3800         newFileLoc.append(" \"");
3801         newFileLoc.append(filedat.getFilePath());
3802         newFileLoc.append("\"");
3803
3804       }
3805       newFileLoc.append(";");
3806     }
3807
3808     if (newFileLoc.length() == 0)
3809     {
3810       return;
3811     }
3812     int histbug = newFileLoc.indexOf("history = ");
3813     if (histbug > -1)
3814     {
3815       /*
3816        * change "history = [true|false];" to "history = [1|0];"
3817        */
3818       histbug += 10;
3819       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3820       String val = (diff == -1) ? null : newFileLoc
3821               .substring(histbug, diff);
3822       if (val != null && val.length() >= 4)
3823       {
3824         if (val.contains("e")) // eh? what can it be?
3825         {
3826           if (val.trim().equals("true"))
3827           {
3828             val = "1";
3829           }
3830           else
3831           {
3832             val = "0";
3833           }
3834           newFileLoc.replace(histbug, diff, val);
3835         }
3836       }
3837     }
3838
3839     final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
3840             .size()]);
3841     final String[] id = pdbids.toArray(new String[pdbids.size()]);
3842     final SequenceI[][] sq = seqmaps
3843             .toArray(new SequenceI[seqmaps.size()][]);
3844     final String fileloc = newFileLoc.toString();
3845     final String sviewid = viewerData.getKey();
3846     final AlignFrame alf = af;
3847     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
3848             svattrib.getWidth(), svattrib.getHeight());
3849     try
3850     {
3851       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
3852       {
3853         @Override
3854         public void run()
3855         {
3856           JalviewStructureDisplayI sview = null;
3857           try
3858           {
3859             sview = new StructureViewer(alf.alignPanel
3860                     .getStructureSelectionManager()).createView(
3861                     StructureViewer.ViewerType.JMOL, pdbf, id, sq,
3862                     alf.alignPanel, svattrib, fileloc, rect, sviewid);
3863             addNewStructureViewer(sview);
3864           } catch (OutOfMemoryError ex)
3865           {
3866             new OOMWarning("restoring structure view for PDB id " + id,
3867                     (OutOfMemoryError) ex.getCause());
3868             if (sview != null && sview.isVisible())
3869             {
3870               sview.closeViewer(false);
3871               sview.setVisible(false);
3872               sview.dispose();
3873             }
3874           }
3875         }
3876       });
3877     } catch (InvocationTargetException ex)
3878     {
3879       warn("Unexpected error when opening Jmol view.", ex);
3880
3881     } catch (InterruptedException e)
3882     {
3883       // e.printStackTrace();
3884     }
3885
3886   }
3887
3888   /**
3889    * Generates a name for the entry in the project jar file to hold state
3890    * information for a structure viewer
3891    * 
3892    * @param viewId
3893    * @return
3894    */
3895   protected String getViewerJarEntryName(String viewId)
3896   {
3897     return VIEWER_PREFIX + viewId;
3898   }
3899
3900   /**
3901    * Returns any open frame that matches given structure viewer data. The match
3902    * is based on the unique viewId, or (for older project versions) the frame's
3903    * geometry.
3904    * 
3905    * @param viewerData
3906    * @return
3907    */
3908   protected StructureViewerBase findMatchingViewer(
3909           Entry<String, StructureViewerModel> viewerData)
3910   {
3911     final String sviewid = viewerData.getKey();
3912     final StructureViewerModel svattrib = viewerData.getValue();
3913     StructureViewerBase comp = null;
3914     JInternalFrame[] frames = getAllFrames();
3915     for (JInternalFrame frame : frames)
3916     {
3917       if (frame instanceof StructureViewerBase)
3918       {
3919         /*
3920          * Post jalview 2.4 schema includes structure view id
3921          */
3922         if (sviewid != null
3923                 && ((StructureViewerBase) frame).getViewId()
3924                         .equals(sviewid))
3925         {
3926           comp = (StructureViewerBase) frame;
3927           break; // break added in 2.9
3928         }
3929         /*
3930          * Otherwise test for matching position and size of viewer frame
3931          */
3932         else if (frame.getX() == svattrib.getX()
3933                 && frame.getY() == svattrib.getY()
3934                 && frame.getHeight() == svattrib.getHeight()
3935                 && frame.getWidth() == svattrib.getWidth())
3936         {
3937           comp = (StructureViewerBase) frame;
3938           // no break in faint hope of an exact match on viewId
3939         }
3940       }
3941     }
3942     return comp;
3943   }
3944
3945   /**
3946    * Link an AlignmentPanel to an existing structure viewer.
3947    * 
3948    * @param ap
3949    * @param viewer
3950    * @param oldFiles
3951    * @param useinViewerSuperpos
3952    * @param usetoColourbyseq
3953    * @param viewerColouring
3954    */
3955   protected void linkStructureViewer(AlignmentPanel ap,
3956           StructureViewerBase viewer, StructureViewerModel stateData)
3957   {
3958     // NOTE: if the jalview project is part of a shared session then
3959     // view synchronization should/could be done here.
3960
3961     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
3962     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
3963     final boolean viewerColouring = stateData.isColourByViewer();
3964     Map<File, StructureData> oldFiles = stateData.getFileData();
3965
3966     /*
3967      * Add mapping for sequences in this view to an already open viewer
3968      */
3969     final AAStructureBindingModel binding = viewer.getBinding();
3970     for (File id : oldFiles.keySet())
3971     {
3972       // add this and any other pdb files that should be present in the
3973       // viewer
3974       StructureData filedat = oldFiles.get(id);
3975       String pdbFile = filedat.getFilePath();
3976       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
3977       binding.getSsm().setMapping(seq, null, pdbFile,
3978               jalview.io.AppletFormatAdapter.FILE);
3979       binding.addSequenceForStructFile(pdbFile, seq);
3980     }
3981     // and add the AlignmentPanel's reference to the view panel
3982     viewer.addAlignmentPanel(ap);
3983     if (useinViewerSuperpos)
3984     {
3985       viewer.useAlignmentPanelForSuperposition(ap);
3986     }
3987     else
3988     {
3989       viewer.excludeAlignmentPanelForSuperposition(ap);
3990     }
3991     if (usetoColourbyseq)
3992     {
3993       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
3994     }
3995     else
3996     {
3997       viewer.excludeAlignmentPanelForColourbyseq(ap);
3998     }
3999   }
4000
4001   /**
4002    * Get all frames within the Desktop.
4003    * 
4004    * @return
4005    */
4006   protected JInternalFrame[] getAllFrames()
4007   {
4008     JInternalFrame[] frames = null;
4009     // TODO is this necessary - is it safe - risk of hanging?
4010     do
4011     {
4012       try
4013       {
4014         frames = Desktop.desktop.getAllFrames();
4015       } catch (ArrayIndexOutOfBoundsException e)
4016       {
4017         // occasional No such child exceptions are thrown here...
4018         try
4019         {
4020           Thread.sleep(10);
4021         } catch (InterruptedException f)
4022         {
4023         }
4024       }
4025     } while (frames == null);
4026     return frames;
4027   }
4028
4029   /**
4030    * 
4031    * @param supported
4032    *          - minimum version we are comparing against
4033    * @param version
4034    *          - version of data being processsed.
4035    * @return true if version is development/null or evaluates to the same or
4036    *         later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*)
4037    */
4038   protected boolean isVersionStringLaterThan(String supported,
4039           String version)
4040   {
4041     if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4042             || version.equalsIgnoreCase("Test")
4043             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4044     {
4045       System.err.println("Assuming project file with "
4046               + (version == null ? "null" : version)
4047               + " is compatible with Jalview version " + supported);
4048       return true;
4049     }
4050     else
4051     {
4052       StringTokenizer currentV = new StringTokenizer(supported, "."), fileV = new StringTokenizer(
4053               version, ".");
4054       while (currentV.hasMoreTokens() && fileV.hasMoreTokens())
4055       {
4056         // convert b to decimal to catch bugfix releases within a series
4057         String curT = currentV.nextToken().toLowerCase().replace('b', '.');
4058         String fileT = fileV.nextToken().toLowerCase().replace('b', '.');
4059         try
4060         {
4061           if (Float.valueOf(curT) > Float.valueOf(fileT))
4062           {
4063             // current version is newer than the version that wrote the file
4064             return false;
4065           }
4066         } catch (NumberFormatException nfe)
4067         {
4068           System.err
4069                   .println("** WARNING: Version comparison failed for tokens ("
4070                           + curT
4071                           + ") and ("
4072                           + fileT
4073                           + ")\n** Current: '"
4074                           + supported + "' and Version: '" + version + "'");
4075         }
4076       }
4077       if (currentV.hasMoreElements())
4078       {
4079         // fileV has no minor version but identical series to current
4080         return false;
4081       }
4082     }
4083     return true;
4084   }
4085
4086   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4087
4088   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4089   {
4090     if (newStructureViewers != null)
4091     {
4092       sview.getBinding().setFinishedLoadingFromArchive(false);
4093       newStructureViewers.add(sview);
4094     }
4095   }
4096
4097   protected void setLoadingFinishedForNewStructureViewers()
4098   {
4099     if (newStructureViewers != null)
4100     {
4101       for (JalviewStructureDisplayI sview : newStructureViewers)
4102       {
4103         sview.getBinding().setFinishedLoadingFromArchive(true);
4104       }
4105       newStructureViewers.clear();
4106       newStructureViewers = null;
4107     }
4108   }
4109
4110   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4111           List<SequenceI> hiddenSeqs, Alignment al,
4112           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4113           String viewId, List<JvAnnotRow> autoAlan)
4114   {
4115     AlignFrame af = null;
4116     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4117             uniqueSeqSetId, viewId);
4118
4119     af.setFileName(file, "Jalview");
4120
4121     for (int i = 0; i < JSEQ.length; i++)
4122     {
4123       af.viewport.setSequenceColour(af.viewport.getAlignment()
4124               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4125     }
4126
4127     af.viewport.setGatherViewsHere(view.getGatheredViews());
4128
4129     if (view.getSequenceSetId() != null)
4130     {
4131       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4132
4133       af.viewport.setSequenceSetId(uniqueSeqSetId);
4134       if (av != null)
4135       {
4136         // propagate shared settings to this new view
4137         af.viewport.setHistoryList(av.getHistoryList());
4138         af.viewport.setRedoList(av.getRedoList());
4139       }
4140       else
4141       {
4142         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4143       }
4144       // TODO: check if this method can be called repeatedly without
4145       // side-effects if alignpanel already registered.
4146       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4147     }
4148     // apply Hidden regions to view.
4149     if (hiddenSeqs != null)
4150     {
4151       for (int s = 0; s < JSEQ.length; s++)
4152       {
4153         jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
4154
4155         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4156         {
4157           hidden.addSequence(
4158                   al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
4159         }
4160         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
4161       }
4162
4163       // jalview.datamodel.SequenceI[] hseqs = new
4164       // jalview.datamodel.SequenceI[hiddenSeqs
4165       // .size()];
4166       //
4167       // for (int s = 0; s < hiddenSeqs.size(); s++)
4168       // {
4169       // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
4170       // }
4171
4172       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4173               .size()]);
4174       af.viewport.hideSequence(hseqs);
4175
4176     }
4177     // recover view properties and display parameters
4178     if (view.getViewName() != null)
4179     {
4180       af.viewport.viewName = view.getViewName();
4181       af.setInitialTabVisible();
4182     }
4183     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4184             view.getHeight());
4185
4186     af.viewport.setShowAnnotation(view.getShowAnnotation());
4187     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4188
4189     af.viewport.setColourText(view.getShowColourText());
4190
4191     af.viewport.setConservationSelected(view.getConservationSelected());
4192     af.viewport.setShowJVSuffix(view.getShowFullId());
4193     af.viewport.setRightAlignIds(view.getRightAlignIds());
4194     af.viewport.setFont(
4195             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4196                     .getFontSize()), true);
4197     // TODO: allow custom charWidth/Heights to be restored by updating them
4198     // after setting font - which means set above to false
4199     af.viewport.setRenderGaps(view.getRenderGaps());
4200     af.viewport.setWrapAlignment(view.getWrapAlignment());
4201     af.viewport.setShowAnnotation(view.getShowAnnotation());
4202
4203     af.viewport.setShowBoxes(view.getShowBoxes());
4204
4205     af.viewport.setShowText(view.getShowText());
4206
4207     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4208     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4209     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4210     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4211             .isShowUnconserved() : false);
4212     af.viewport.setStartRes(view.getStartRes());
4213     af.viewport.setStartSeq(view.getStartSeq());
4214     af.alignPanel.updateLayout();
4215     ColourSchemeI cs = null;
4216     // apply colourschemes
4217     if (view.getBgColour() != null)
4218     {
4219       if (view.getBgColour().startsWith("ucs"))
4220       {
4221         cs = getUserColourScheme(jms, view.getBgColour());
4222       }
4223       else if (view.getBgColour().startsWith("Annotation"))
4224       {
4225         AnnotationColours viewAnnColour = view.getAnnotationColours();
4226         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4227
4228         // annpos
4229
4230       }
4231       else
4232       {
4233         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4234       }
4235
4236       if (cs != null)
4237       {
4238         cs.setThreshold(view.getPidThreshold(), true);
4239         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4240       }
4241     }
4242
4243     af.viewport.setGlobalColourScheme(cs);
4244     af.viewport.setColourAppliesToAllGroups(false);
4245
4246     if (view.getConservationSelected() && cs != null)
4247     {
4248       cs.setConservationInc(view.getConsThreshold());
4249     }
4250
4251     af.changeColour(cs);
4252
4253     af.viewport.setColourAppliesToAllGroups(true);
4254
4255     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4256
4257     if (view.hasCentreColumnLabels())
4258     {
4259       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4260     }
4261     if (view.hasIgnoreGapsinConsensus())
4262     {
4263       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4264               null);
4265     }
4266     if (view.hasFollowHighlight())
4267     {
4268       af.viewport.setFollowHighlight(view.getFollowHighlight());
4269     }
4270     if (view.hasFollowSelection())
4271     {
4272       af.viewport.followSelection = view.getFollowSelection();
4273     }
4274     if (view.hasShowConsensusHistogram())
4275     {
4276       af.viewport.setShowConsensusHistogram(view
4277               .getShowConsensusHistogram());
4278     }
4279     else
4280     {
4281       af.viewport.setShowConsensusHistogram(true);
4282     }
4283     if (view.hasShowSequenceLogo())
4284     {
4285       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4286     }
4287     else
4288     {
4289       af.viewport.setShowSequenceLogo(false);
4290     }
4291     if (view.hasNormaliseSequenceLogo())
4292     {
4293       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4294     }
4295     if (view.hasShowDbRefTooltip())
4296     {
4297       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4298     }
4299     if (view.hasShowNPfeatureTooltip())
4300     {
4301       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4302     }
4303     if (view.hasShowGroupConsensus())
4304     {
4305       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4306     }
4307     else
4308     {
4309       af.viewport.setShowGroupConsensus(false);
4310     }
4311     if (view.hasShowGroupConservation())
4312     {
4313       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4314     }
4315     else
4316     {
4317       af.viewport.setShowGroupConservation(false);
4318     }
4319
4320     // recover featre settings
4321     if (jms.getFeatureSettings() != null)
4322     {
4323       FeaturesDisplayed fdi;
4324       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4325       String[] renderOrder = new String[jms.getFeatureSettings()
4326               .getSettingCount()];
4327       Hashtable featureGroups = new Hashtable();
4328       Hashtable featureColours = new Hashtable();
4329       Hashtable featureOrder = new Hashtable();
4330
4331       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4332       {
4333         Setting setting = jms.getFeatureSettings().getSetting(fs);
4334         if (setting.hasMincolour())
4335         {
4336           GraduatedColor gc = setting.hasMin() ? new GraduatedColor(
4337                   new java.awt.Color(setting.getMincolour()),
4338                   new java.awt.Color(setting.getColour()),
4339                   setting.getMin(), setting.getMax()) : new GraduatedColor(
4340                   new java.awt.Color(setting.getMincolour()),
4341                   new java.awt.Color(setting.getColour()), 0, 1);
4342           if (setting.hasThreshold())
4343           {
4344             gc.setThresh(setting.getThreshold());
4345             gc.setThreshType(setting.getThreshstate());
4346           }
4347           gc.setAutoScaled(true); // default
4348           if (setting.hasAutoScale())
4349           {
4350             gc.setAutoScaled(setting.getAutoScale());
4351           }
4352           if (setting.hasColourByLabel())
4353           {
4354             gc.setColourByLabel(setting.getColourByLabel());
4355           }
4356           // and put in the feature colour table.
4357           featureColours.put(setting.getType(), gc);
4358         }
4359         else
4360         {
4361           featureColours.put(setting.getType(),
4362                   new java.awt.Color(setting.getColour()));
4363         }
4364         renderOrder[fs] = setting.getType();
4365         if (setting.hasOrder())
4366         {
4367           featureOrder.put(setting.getType(), setting.getOrder());
4368         }
4369         else
4370         {
4371           featureOrder.put(setting.getType(), new Float(fs
4372                   / jms.getFeatureSettings().getSettingCount()));
4373         }
4374         if (setting.getDisplay())
4375         {
4376           fdi.setVisible(setting.getType());
4377         }
4378       }
4379       Hashtable fgtable = new Hashtable();
4380       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4381       {
4382         Group grp = jms.getFeatureSettings().getGroup(gs);
4383         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4384       }
4385       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4386       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4387       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4388       FeatureRendererSettings frs = new FeatureRendererSettings(
4389               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4390       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4391               .transferSettings(frs);
4392
4393     }
4394
4395     if (view.getHiddenColumnsCount() > 0)
4396     {
4397       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4398       {
4399         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4400                 .getHiddenColumns(c).getEnd() // +1
4401                 );
4402       }
4403     }
4404     if (view.getCalcIdParam() != null)
4405     {
4406       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4407       {
4408         if (calcIdParam != null)
4409         {
4410           if (recoverCalcIdParam(calcIdParam, af.viewport))
4411           {
4412           }
4413           else
4414           {
4415             warn("Couldn't recover parameters for "
4416                     + calcIdParam.getCalcId());
4417           }
4418         }
4419       }
4420     }
4421     af.setMenusFromViewport(af.viewport);
4422
4423     // TODO: we don't need to do this if the viewport is aready visible.
4424     /*
4425      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4426      * has a 'cdna/protein complement' view, in which case save it in order to
4427      * populate a SplitFrame once all views have been read in.
4428      */
4429     String complementaryViewId = view.getComplementId();
4430     if (complementaryViewId == null)
4431     {
4432       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4433               view.getHeight());
4434       // recompute any autoannotation
4435       af.alignPanel.updateAnnotation(false, true);
4436       reorderAutoannotation(af, al, autoAlan);
4437       af.alignPanel.alignmentChanged();
4438     }
4439     else
4440     {
4441       splitFrameCandidates.put(view, af);
4442     }
4443     return af;
4444   }
4445
4446   private ColourSchemeI constructAnnotationColour(
4447           AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
4448           JalviewModelSequence jms, boolean checkGroupAnnColour)
4449   {
4450     boolean propagateAnnColour = false;
4451     ColourSchemeI cs = null;
4452     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4453     if (checkGroupAnnColour && al.getGroups() != null
4454             && al.getGroups().size() > 0)
4455     {
4456       // pre 2.8.1 behaviour
4457       // check to see if we should transfer annotation colours
4458       propagateAnnColour = true;
4459       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4460       {
4461         if (sg.cs instanceof AnnotationColourGradient)
4462         {
4463           propagateAnnColour = false;
4464         }
4465       }
4466     }
4467     // int find annotation
4468     if (annAlignment.getAlignmentAnnotation() != null)
4469     {
4470       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4471       {
4472         if (annAlignment.getAlignmentAnnotation()[i].label
4473                 .equals(viewAnnColour.getAnnotation()))
4474         {
4475           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4476           {
4477             annAlignment.getAlignmentAnnotation()[i]
4478                     .setThreshold(new jalview.datamodel.GraphLine(
4479                             viewAnnColour.getThreshold(), "Threshold",
4480                             java.awt.Color.black)
4481
4482                     );
4483           }
4484
4485           if (viewAnnColour.getColourScheme().equals("None"))
4486           {
4487             cs = new AnnotationColourGradient(
4488                     annAlignment.getAlignmentAnnotation()[i],
4489                     new java.awt.Color(viewAnnColour.getMinColour()),
4490                     new java.awt.Color(viewAnnColour.getMaxColour()),
4491                     viewAnnColour.getAboveThreshold());
4492           }
4493           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4494           {
4495             cs = new AnnotationColourGradient(
4496                     annAlignment.getAlignmentAnnotation()[i],
4497                     getUserColourScheme(jms,
4498                             viewAnnColour.getColourScheme()),
4499                     viewAnnColour.getAboveThreshold());
4500           }
4501           else
4502           {
4503             cs = new AnnotationColourGradient(
4504                     annAlignment.getAlignmentAnnotation()[i],
4505                     ColourSchemeProperty.getColour(al,
4506                             viewAnnColour.getColourScheme()),
4507                     viewAnnColour.getAboveThreshold());
4508           }
4509           if (viewAnnColour.hasPerSequence())
4510           {
4511             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4512                     .isPerSequence());
4513           }
4514           if (viewAnnColour.hasPredefinedColours())
4515           {
4516             ((AnnotationColourGradient) cs)
4517                     .setPredefinedColours(viewAnnColour
4518                             .isPredefinedColours());
4519           }
4520           if (propagateAnnColour && al.getGroups() != null)
4521           {
4522             // Also use these settings for all the groups
4523             for (int g = 0; g < al.getGroups().size(); g++)
4524             {
4525               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4526
4527               if (sg.cs == null)
4528               {
4529                 continue;
4530               }
4531
4532               /*
4533                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4534                * new AnnotationColourGradient(
4535                * annAlignment.getAlignmentAnnotation()[i], new
4536                * java.awt.Color(viewAnnColour. getMinColour()), new
4537                * java.awt.Color(viewAnnColour. getMaxColour()),
4538                * viewAnnColour.getAboveThreshold()); } else
4539                */
4540               {
4541                 sg.cs = new AnnotationColourGradient(
4542                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4543                         viewAnnColour.getAboveThreshold());
4544                 if (cs instanceof AnnotationColourGradient)
4545                 {
4546                   if (viewAnnColour.hasPerSequence())
4547                   {
4548                     ((AnnotationColourGradient) cs)
4549                             .setSeqAssociated(viewAnnColour.isPerSequence());
4550                   }
4551                   if (viewAnnColour.hasPredefinedColours())
4552                   {
4553                     ((AnnotationColourGradient) cs)
4554                             .setPredefinedColours(viewAnnColour
4555                                     .isPredefinedColours());
4556                   }
4557                 }
4558               }
4559
4560             }
4561           }
4562
4563           break;
4564         }
4565
4566       }
4567     }
4568     return cs;
4569   }
4570
4571   private void reorderAutoannotation(AlignFrame af, Alignment al,
4572           List<JvAnnotRow> autoAlan)
4573   {
4574     // copy over visualization settings for autocalculated annotation in the
4575     // view
4576     if (al.getAlignmentAnnotation() != null)
4577     {
4578       /**
4579        * Kludge for magic autoannotation names (see JAL-811)
4580        */
4581       String[] magicNames = new String[]
4582       { "Consensus", "Quality", "Conservation" };
4583       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4584       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4585       for (String nm : magicNames)
4586       {
4587         visan.put(nm, nullAnnot);
4588       }
4589       for (JvAnnotRow auan : autoAlan)
4590       {
4591         visan.put(auan.template.label
4592                 + (auan.template.getCalcId() == null ? "" : "\t"
4593                         + auan.template.getCalcId()), auan);
4594       }
4595       int hSize = al.getAlignmentAnnotation().length;
4596       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4597       // work through any autoCalculated annotation already on the view
4598       // removing it if it should be placed in a different location on the
4599       // annotation panel.
4600       List<String> remains = new ArrayList<String>(visan.keySet());
4601       for (int h = 0; h < hSize; h++)
4602       {
4603         jalview.datamodel.AlignmentAnnotation jalan = al
4604                 .getAlignmentAnnotation()[h];
4605         if (jalan.autoCalculated)
4606         {
4607           String k;
4608           JvAnnotRow valan = visan.get(k = jalan.label);
4609           if (jalan.getCalcId() != null)
4610           {
4611             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4612           }
4613
4614           if (valan != null)
4615           {
4616             // delete the auto calculated row from the alignment
4617             al.deleteAnnotation(jalan, false);
4618             remains.remove(k);
4619             hSize--;
4620             h--;
4621             if (valan != nullAnnot)
4622             {
4623               if (jalan != valan.template)
4624               {
4625                 // newly created autoannotation row instance
4626                 // so keep a reference to the visible annotation row
4627                 // and copy over all relevant attributes
4628                 if (valan.template.graphHeight >= 0)
4629
4630                 {
4631                   jalan.graphHeight = valan.template.graphHeight;
4632                 }
4633                 jalan.visible = valan.template.visible;
4634               }
4635               reorder.add(new JvAnnotRow(valan.order, jalan));
4636             }
4637           }
4638         }
4639       }
4640       // Add any (possibly stale) autocalculated rows that were not appended to
4641       // the view during construction
4642       for (String other : remains)
4643       {
4644         JvAnnotRow othera = visan.get(other);
4645         if (othera != nullAnnot && othera.template.getCalcId() != null
4646                 && othera.template.getCalcId().length() > 0)
4647         {
4648           reorder.add(othera);
4649         }
4650       }
4651       // now put the automatic annotation in its correct place
4652       int s = 0, srt[] = new int[reorder.size()];
4653       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4654       for (JvAnnotRow jvar : reorder)
4655       {
4656         rws[s] = jvar;
4657         srt[s++] = jvar.order;
4658       }
4659       reorder.clear();
4660       jalview.util.QuickSort.sort(srt, rws);
4661       // and re-insert the annotation at its correct position
4662       for (JvAnnotRow jvar : rws)
4663       {
4664         al.addAnnotation(jvar.template, jvar.order);
4665       }
4666       af.alignPanel.adjustAnnotationHeight();
4667     }
4668   }
4669
4670   Hashtable skipList = null;
4671
4672   /**
4673    * TODO remove this method
4674    * 
4675    * @param view
4676    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4677    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4678    *         throw new Error("Implementation Error. No skipList defined for this
4679    *         Jalview2XML instance."); } return (AlignFrame)
4680    *         skipList.get(view.getSequenceSetId()); }
4681    */
4682
4683   /**
4684    * Check if the Jalview view contained in object should be skipped or not.
4685    * 
4686    * @param object
4687    * @return true if view's sequenceSetId is a key in skipList
4688    */
4689   private boolean skipViewport(JalviewModel object)
4690   {
4691     if (skipList == null)
4692     {
4693       return false;
4694     }
4695     String id;
4696     if (skipList.containsKey(id = object.getJalviewModelSequence()
4697             .getViewport()[0].getSequenceSetId()))
4698     {
4699       if (Cache.log != null && Cache.log.isDebugEnabled())
4700       {
4701         Cache.log.debug("Skipping seuqence set id " + id);
4702       }
4703       return true;
4704     }
4705     return false;
4706   }
4707
4708   public void addToSkipList(AlignFrame af)
4709   {
4710     if (skipList == null)
4711     {
4712       skipList = new Hashtable();
4713     }
4714     skipList.put(af.getViewport().getSequenceSetId(), af);
4715   }
4716
4717   public void clearSkipList()
4718   {
4719     if (skipList != null)
4720     {
4721       skipList.clear();
4722       skipList = null;
4723     }
4724   }
4725
4726   private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
4727           boolean ignoreUnrefed)
4728   {
4729     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
4730     Vector dseqs = null;
4731     if (ds == null)
4732     {
4733       // create a list of new dataset sequences
4734       dseqs = new Vector();
4735     }
4736     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4737     {
4738       Sequence vamsasSeq = vamsasSet.getSequence(i);
4739       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4740     }
4741     // create a new dataset
4742     if (ds == null)
4743     {
4744       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4745       dseqs.copyInto(dsseqs);
4746       ds = new jalview.datamodel.Alignment(dsseqs);
4747       debug("Created new dataset " + vamsasSet.getDatasetId()
4748               + " for alignment " + System.identityHashCode(al));
4749       addDatasetRef(vamsasSet.getDatasetId(), ds);
4750     }
4751     // set the dataset for the newly imported alignment.
4752     if (al.getDataset() == null && !ignoreUnrefed)
4753     {
4754       al.setDataset(ds);
4755     }
4756   }
4757
4758   /**
4759    * 
4760    * @param vamsasSeq
4761    *          sequence definition to create/merge dataset sequence for
4762    * @param ds
4763    *          dataset alignment
4764    * @param dseqs
4765    *          vector to add new dataset sequence to
4766    */
4767   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4768           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4769   {
4770     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4771     // xRef Codon Maps
4772     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4773     SequenceI dsq = null;
4774     if (sq != null && sq.getDatasetSequence() != null)
4775     {
4776       dsq = sq.getDatasetSequence();
4777     }
4778     if (sq == null && ignoreUnrefed)
4779     {
4780       return;
4781     }
4782     String sqid = vamsasSeq.getDsseqid();
4783     if (dsq == null)
4784     {
4785       // need to create or add a new dataset sequence reference to this sequence
4786       if (sqid != null)
4787       {
4788         dsq = seqRefIds.get(sqid);
4789       }
4790       // check again
4791       if (dsq == null)
4792       {
4793         // make a new dataset sequence
4794         dsq = sq.createDatasetSequence();
4795         if (sqid == null)
4796         {
4797           // make up a new dataset reference for this sequence
4798           sqid = seqHash(dsq);
4799         }
4800         dsq.setVamsasId(uniqueSetSuffix + sqid);
4801         seqRefIds.put(sqid, dsq);
4802         if (ds == null)
4803         {
4804           if (dseqs != null)
4805           {
4806             dseqs.addElement(dsq);
4807           }
4808         }
4809         else
4810         {
4811           ds.addSequence(dsq);
4812         }
4813       }
4814       else
4815       {
4816         if (sq != dsq)
4817         { // make this dataset sequence sq's dataset sequence
4818           sq.setDatasetSequence(dsq);
4819           // and update the current dataset alignment
4820           if (ds == null)
4821           {
4822             if (dseqs != null)
4823             {
4824               if (!dseqs.contains(dsq))
4825               {
4826                 dseqs.add(dsq);
4827               }
4828             }
4829             else
4830             {
4831               if (ds.findIndex(dsq) < 0)
4832               {
4833                 ds.addSequence(dsq);
4834               }
4835             }
4836           }
4837         }
4838       }
4839     }
4840     // TODO: refactor this as a merge dataset sequence function
4841     // now check that sq (the dataset sequence) sequence really is the union of
4842     // all references to it
4843     // boolean pre = sq.getStart() < dsq.getStart();
4844     // boolean post = sq.getEnd() > dsq.getEnd();
4845     // if (pre || post)
4846     if (sq != dsq)
4847     {
4848       // StringBuffer sb = new StringBuffer();
4849       String newres = jalview.analysis.AlignSeq.extractGaps(
4850               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4851       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4852               && newres.length() > dsq.getLength())
4853       {
4854         // Update with the longer sequence.
4855         synchronized (dsq)
4856         {
4857           /*
4858            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4859            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4860            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4861            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4862            */
4863           dsq.setSequence(newres);
4864         }
4865         // TODO: merges will never happen if we 'know' we have the real dataset
4866         // sequence - this should be detected when id==dssid
4867         System.err
4868                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4869         // + (pre ? "prepended" : "") + " "
4870         // + (post ? "appended" : ""));
4871       }
4872     }
4873   }
4874
4875   /*
4876    * TODO use AlignmentI here and in related methods - needs
4877    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
4878    */
4879   Hashtable<String, Alignment> datasetIds = null;
4880
4881   IdentityHashMap<Alignment, String> dataset2Ids = null;
4882
4883   private Alignment getDatasetFor(String datasetId)
4884   {
4885     if (datasetIds == null)
4886     {
4887       datasetIds = new Hashtable<String, Alignment>();
4888       return null;
4889     }
4890     if (datasetIds.containsKey(datasetId))
4891     {
4892       return datasetIds.get(datasetId);
4893     }
4894     return null;
4895   }
4896
4897   private void addDatasetRef(String datasetId, Alignment dataset)
4898   {
4899     if (datasetIds == null)
4900     {
4901       datasetIds = new Hashtable<String, Alignment>();
4902     }
4903     datasetIds.put(datasetId, dataset);
4904   }
4905
4906   /**
4907    * make a new dataset ID for this jalview dataset alignment
4908    * 
4909    * @param dataset
4910    * @return
4911    */
4912   private String getDatasetIdRef(Alignment dataset)
4913   {
4914     if (dataset.getDataset() != null)
4915     {
4916       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4917     }
4918     String datasetId = makeHashCode(dataset, null);
4919     if (datasetId == null)
4920     {
4921       // make a new datasetId and record it
4922       if (dataset2Ids == null)
4923       {
4924         dataset2Ids = new IdentityHashMap<Alignment, String>();
4925       }
4926       else
4927       {
4928         datasetId = dataset2Ids.get(dataset);
4929       }
4930       if (datasetId == null)
4931       {
4932         datasetId = "ds" + dataset2Ids.size() + 1;
4933         dataset2Ids.put(dataset, datasetId);
4934       }
4935     }
4936     return datasetId;
4937   }
4938
4939   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4940   {
4941     for (int d = 0; d < sequence.getDBRefCount(); d++)
4942     {
4943       DBRef dr = sequence.getDBRef(d);
4944       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4945               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4946                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4947       if (dr.getMapping() != null)
4948       {
4949         entry.setMap(addMapping(dr.getMapping()));
4950       }
4951       datasetSequence.addDBRef(entry);
4952     }
4953   }
4954
4955   private jalview.datamodel.Mapping addMapping(Mapping m)
4956   {
4957     SequenceI dsto = null;
4958     // Mapping m = dr.getMapping();
4959     int fr[] = new int[m.getMapListFromCount() * 2];
4960     Enumeration f = m.enumerateMapListFrom();
4961     for (int _i = 0; f.hasMoreElements(); _i += 2)
4962     {
4963       MapListFrom mf = (MapListFrom) f.nextElement();
4964       fr[_i] = mf.getStart();
4965       fr[_i + 1] = mf.getEnd();
4966     }
4967     int fto[] = new int[m.getMapListToCount() * 2];
4968     f = m.enumerateMapListTo();
4969     for (int _i = 0; f.hasMoreElements(); _i += 2)
4970     {
4971       MapListTo mf = (MapListTo) f.nextElement();
4972       fto[_i] = mf.getStart();
4973       fto[_i + 1] = mf.getEnd();
4974     }
4975     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
4976             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
4977     if (m.getMappingChoice() != null)
4978     {
4979       MappingChoice mc = m.getMappingChoice();
4980       if (mc.getDseqFor() != null)
4981       {
4982         String dsfor = "" + mc.getDseqFor();
4983         if (seqRefIds.containsKey(dsfor))
4984         {
4985           /**
4986            * recover from hash
4987            */
4988           jmap.setTo(seqRefIds.get(dsfor));
4989         }
4990         else
4991         {
4992           frefedSequence.add(new Object[]
4993           { dsfor, jmap });
4994         }
4995       }
4996       else
4997       {
4998         /**
4999          * local sequence definition
5000          */
5001         Sequence ms = mc.getSequence();
5002         SequenceI djs = null;
5003         String sqid = ms.getDsseqid();
5004         if (sqid != null && sqid.length() > 0)
5005         {
5006           /*
5007            * recover dataset sequence
5008            */
5009           djs = seqRefIds.get(sqid);
5010         }
5011         else
5012         {
5013           System.err
5014                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5015           sqid = ((Object) ms).toString(); // make up a new hascode for
5016           // undefined dataset sequence hash
5017           // (unlikely to happen)
5018         }
5019
5020         if (djs == null)
5021         {
5022           /**
5023            * make a new dataset sequence and add it to refIds hash
5024            */
5025           djs = new jalview.datamodel.Sequence(ms.getName(),
5026                   ms.getSequence());
5027           djs.setStart(jmap.getMap().getToLowest());
5028           djs.setEnd(jmap.getMap().getToHighest());
5029           djs.setVamsasId(uniqueSetSuffix + sqid);
5030           jmap.setTo(djs);
5031           seqRefIds.put(sqid, djs);
5032
5033         }
5034         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5035         addDBRefs(djs, ms);
5036
5037       }
5038     }
5039     return (jmap);
5040
5041   }
5042
5043   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5044           boolean keepSeqRefs)
5045   {
5046     initSeqRefs();
5047     JalviewModel jm = saveState(ap, null, null, null);
5048
5049     if (!keepSeqRefs)
5050     {
5051       clearSeqRefs();
5052       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5053     }
5054     else
5055     {
5056       uniqueSetSuffix = "";
5057       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5058       // overwrite the
5059       // view we just
5060       // copied
5061     }
5062     if (this.frefedSequence == null)
5063     {
5064       frefedSequence = new Vector();
5065     }
5066
5067     viewportsAdded.clear();
5068
5069     AlignFrame af = loadFromObject(jm, null, false, null);
5070     af.alignPanels.clear();
5071     af.closeMenuItem_actionPerformed(true);
5072
5073     /*
5074      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5075      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5076      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5077      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5078      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5079      */
5080
5081     return af.alignPanel;
5082   }
5083
5084   /**
5085    * flag indicating if hashtables should be cleared on finalization TODO this
5086    * flag may not be necessary
5087    */
5088   private final boolean _cleartables = true;
5089
5090   private Hashtable jvids2vobj;
5091
5092   /*
5093    * (non-Javadoc)
5094    * 
5095    * @see java.lang.Object#finalize()
5096    */
5097   @Override
5098   protected void finalize() throws Throwable
5099   {
5100     // really make sure we have no buried refs left.
5101     if (_cleartables)
5102     {
5103       clearSeqRefs();
5104     }
5105     this.seqRefIds = null;
5106     this.seqsToIds = null;
5107     super.finalize();
5108   }
5109
5110   private void warn(String msg)
5111   {
5112     warn(msg, null);
5113   }
5114
5115   private void warn(String msg, Exception e)
5116   {
5117     if (Cache.log != null)
5118     {
5119       if (e != null)
5120       {
5121         Cache.log.warn(msg, e);
5122       }
5123       else
5124       {
5125         Cache.log.warn(msg);
5126       }
5127     }
5128     else
5129     {
5130       System.err.println("Warning: " + msg);
5131       if (e != null)
5132       {
5133         e.printStackTrace();
5134       }
5135     }
5136   }
5137
5138   private void debug(String string)
5139   {
5140     debug(string, null);
5141   }
5142
5143   private void debug(String msg, Exception e)
5144   {
5145     if (Cache.log != null)
5146     {
5147       if (e != null)
5148       {
5149         Cache.log.debug(msg, e);
5150       }
5151       else
5152       {
5153         Cache.log.debug(msg);
5154       }
5155     }
5156     else
5157     {
5158       System.err.println("Warning: " + msg);
5159       if (e != null)
5160       {
5161         e.printStackTrace();
5162       }
5163     }
5164   }
5165
5166   /**
5167    * set the object to ID mapping tables used to write/recover objects and XML
5168    * ID strings for the jalview project. If external tables are provided then
5169    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5170    * object goes out of scope. - also populates the datasetIds hashtable with
5171    * alignment objects containing dataset sequences
5172    * 
5173    * @param vobj2jv
5174    *          Map from ID strings to jalview datamodel
5175    * @param jv2vobj
5176    *          Map from jalview datamodel to ID strings
5177    * 
5178    * 
5179    */
5180   public void setObjectMappingTables(Hashtable vobj2jv,
5181           IdentityHashMap jv2vobj)
5182   {
5183     this.jv2vobj = jv2vobj;
5184     this.vobj2jv = vobj2jv;
5185     Iterator ds = jv2vobj.keySet().iterator();
5186     String id;
5187     while (ds.hasNext())
5188     {
5189       Object jvobj = ds.next();
5190       id = jv2vobj.get(jvobj).toString();
5191       if (jvobj instanceof jalview.datamodel.Alignment)
5192       {
5193         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5194         {
5195           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5196         }
5197       }
5198       else if (jvobj instanceof jalview.datamodel.Sequence)
5199       {
5200         // register sequence object so the XML parser can recover it.
5201         if (seqRefIds == null)
5202         {
5203           seqRefIds = new HashMap<String, SequenceI>();
5204         }
5205         if (seqsToIds == null)
5206         {
5207           seqsToIds = new IdentityHashMap<SequenceI, String>();
5208         }
5209         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5210         seqsToIds.put((SequenceI) jvobj, id);
5211       }
5212       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5213       {
5214         String anid;
5215         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5216         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5217         if (jvann.annotationId == null)
5218         {
5219           jvann.annotationId = anid;
5220         }
5221         if (!jvann.annotationId.equals(anid))
5222         {
5223           // TODO verify that this is the correct behaviour
5224           this.warn("Overriding Annotation ID for " + anid
5225                   + " from different id : " + jvann.annotationId);
5226           jvann.annotationId = anid;
5227         }
5228       }
5229       else if (jvobj instanceof String)
5230       {
5231         if (jvids2vobj == null)
5232         {
5233           jvids2vobj = new Hashtable();
5234           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5235         }
5236       }
5237       else
5238       {
5239         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5240       }
5241     }
5242   }
5243
5244   /**
5245    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5246    * objects created from the project archive. If string is null (default for
5247    * construction) then suffix will be set automatically.
5248    * 
5249    * @param string
5250    */
5251   public void setUniqueSetSuffix(String string)
5252   {
5253     uniqueSetSuffix = string;
5254
5255   }
5256
5257   /**
5258    * uses skipList2 as the skipList for skipping views on sequence sets
5259    * associated with keys in the skipList
5260    * 
5261    * @param skipList2
5262    */
5263   public void setSkipList(Hashtable skipList2)
5264   {
5265     skipList = skipList2;
5266   }
5267
5268   /**
5269    * Reads the jar entry of given name and returns its contents, or null if the
5270    * entry is not found.
5271    * 
5272    * @param jprovider
5273    * @param jarEntryName
5274    * @return
5275    */
5276   protected String readJarEntry(jarInputStreamProvider jprovider,
5277           String jarEntryName)
5278   {
5279     String result = null;
5280     BufferedReader in = null;
5281
5282     try
5283     {
5284       /*
5285        * Reopen the jar input stream and traverse its entries to find a matching
5286        * name
5287        */
5288       JarInputStream jin = jprovider.getJarInputStream();
5289       JarEntry entry = null;
5290       do
5291       {
5292         entry = jin.getNextJarEntry();
5293       } while (entry != null && !entry.getName().equals(jarEntryName));
5294
5295       if (entry != null)
5296       {
5297         StringBuilder out = new StringBuilder(256);
5298         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5299         String data;
5300
5301         while ((data = in.readLine()) != null)
5302         {
5303           out.append(data);
5304         }
5305         result = out.toString();
5306       }
5307       else
5308       {
5309         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5310       }
5311     } catch (Exception ex)
5312     {
5313       ex.printStackTrace();
5314     } finally
5315     {
5316       if (in != null)
5317       {
5318         try
5319         {
5320           in.close();
5321         } catch (IOException e)
5322         {
5323           // ignore
5324         }
5325       }
5326     }
5327
5328     return result;
5329   }
5330
5331   /**
5332    * Returns an incrementing counter (0, 1, 2...)
5333    * 
5334    * @return
5335    */
5336   private synchronized int nextCounter()
5337   {
5338     return counter++;
5339   }
5340 }