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