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