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