e4435124d9e4d5f39b23dfdf92c275c1f4856ef8
[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     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3485             af.alignPanel, pdbArray,
3486             seqsArray, colourByChimera, colourBySequence, newViewId);
3487     cvf.setSize(data.getWidth(), data.getHeight());
3488     cvf.setLocation(data.getX(), data.getY());
3489   }
3490
3491   /**
3492    * Create a new Jmol window. First parse the Jmol state to translate filenames
3493    * loaded into the view, and record the order in which files are shown in the
3494    * Jmol view, so we can add the sequence mappings in same order.
3495    * 
3496    * @param viewerData
3497    * @param af
3498    * @param jprovider
3499    */
3500   protected void createJmolViewer(
3501           final Entry<String, StructureViewerModel> viewerData,
3502           AlignFrame af, jarInputStreamProvider jprovider)
3503   {
3504     final StructureViewerModel svattrib = viewerData.getValue();
3505     String state = svattrib.getStateData();
3506
3507     /*
3508      * Pre-2.9: state element value is the Jmol state string
3509      * 
3510      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3511      * + viewId
3512      */
3513     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3514     {
3515       state = readJarEntry(jprovider,
3516               getViewerJarEntryName(svattrib.getViewId()));
3517     }
3518
3519     List<String> pdbfilenames = new ArrayList<String>();
3520     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3521     List<String> pdbids = new ArrayList<String>();
3522     StringBuilder newFileLoc = new StringBuilder(64);
3523     int cp = 0, ncp, ecp;
3524     Map<File, StructureData> oldFiles = svattrib.getFileData();
3525     while ((ncp = state.indexOf("load ", cp)) > -1)
3526     {
3527       do
3528       {
3529         // look for next filename in load statement
3530         newFileLoc.append(state.substring(cp,
3531                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3532         String oldfilenam = state.substring(ncp,
3533                 ecp = state.indexOf("\"", ncp));
3534         // recover the new mapping data for this old filename
3535         // have to normalize filename - since Jmol and jalview do
3536         // filename
3537         // translation differently.
3538         StructureData filedat = oldFiles.get(new File(oldfilenam));
3539         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3540         pdbfilenames.add(filedat.getFilePath());
3541         pdbids.add(filedat.getPdbId());
3542         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3543         newFileLoc.append("\"");
3544         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3545                       // look for next file statement.
3546       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3547     }
3548     if (cp > 0)
3549     {
3550       // just append rest of state
3551       newFileLoc.append(state.substring(cp));
3552     }
3553     else
3554     {
3555       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3556       newFileLoc = new StringBuilder(state);
3557       newFileLoc.append("; load append ");
3558       for (File id : oldFiles.keySet())
3559       {
3560         // add this and any other pdb files that should be present in
3561         // the viewer
3562         StructureData filedat = oldFiles.get(id);
3563         newFileLoc.append(filedat.getFilePath());
3564         pdbfilenames.add(filedat.getFilePath());
3565         pdbids.add(filedat.getPdbId());
3566         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3567         newFileLoc.append(" \"");
3568         newFileLoc.append(filedat.getFilePath());
3569         newFileLoc.append("\"");
3570
3571       }
3572       newFileLoc.append(";");
3573     }
3574
3575     if (newFileLoc.length() > 0)
3576     {
3577       int histbug = newFileLoc.indexOf("history = ");
3578       histbug += 10;
3579       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3580       String val = (diff == -1) ? null : newFileLoc
3581               .substring(histbug, diff);
3582       if (val != null && val.length() >= 4)
3583       {
3584         if (val.contains("e"))
3585         {
3586           if (val.trim().equals("true"))
3587           {
3588             val = "1";
3589           }
3590           else
3591           {
3592             val = "0";
3593           }
3594           newFileLoc.replace(histbug, diff, val);
3595         }
3596       }
3597
3598       final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
3599               .size()]);
3600       final String[] id = pdbids.toArray(new String[pdbids.size()]);
3601       final SequenceI[][] sq = seqmaps
3602               .toArray(new SequenceI[seqmaps.size()][]);
3603       final String fileloc = newFileLoc.toString();
3604       final String sviewid = viewerData.getKey();
3605       final AlignFrame alf = af;
3606       final Rectangle rect = new Rectangle(svattrib.getX(),
3607               svattrib.getY(), svattrib.getWidth(), svattrib.getHeight());
3608       try
3609       {
3610         javax.swing.SwingUtilities.invokeAndWait(new Runnable()
3611         {
3612           @Override
3613           public void run()
3614           {
3615             JalviewStructureDisplayI sview = null;
3616             try
3617             {
3618               sview = new StructureViewer(alf.alignPanel
3619                       .getStructureSelectionManager()).createView(
3620                       StructureViewer.ViewerType.JMOL, pdbf, id, sq,
3621                       alf.alignPanel, svattrib, fileloc, rect, sviewid);
3622               addNewStructureViewer(sview);
3623             } catch (OutOfMemoryError ex)
3624             {
3625               new OOMWarning("restoring structure view for PDB id " + id,
3626                       (OutOfMemoryError) ex.getCause());
3627               if (sview != null && sview.isVisible())
3628               {
3629                 sview.closeViewer(false);
3630                 sview.setVisible(false);
3631                 sview.dispose();
3632               }
3633             }
3634           }
3635         });
3636       } catch (InvocationTargetException ex)
3637       {
3638         warn("Unexpected error when opening Jmol view.", ex);
3639
3640       } catch (InterruptedException e)
3641       {
3642         // e.printStackTrace();
3643       }
3644     }
3645   }
3646
3647   /**
3648    * Generates a name for the entry in the project jar file to hold state
3649    * information for a structure viewer
3650    * 
3651    * @param viewId
3652    * @return
3653    */
3654   protected String getViewerJarEntryName(String viewId)
3655   {
3656     return "viewer_" + viewId;
3657   }
3658
3659   /**
3660    * Returns any open frame that matches given structure viewer data. The match
3661    * is based on the unique viewId, or (for older project versions) the frame's
3662    * geometry.
3663    * 
3664    * @param viewerData
3665    * @return
3666    */
3667   protected StructureViewerBase findMatchingViewer(
3668           Entry<String, StructureViewerModel> viewerData)
3669   {
3670     final String sviewid = viewerData.getKey();
3671     final StructureViewerModel svattrib = viewerData.getValue();
3672     StructureViewerBase comp = null;
3673     JInternalFrame[] frames = getAllFrames();
3674     for (JInternalFrame frame : frames)
3675     {
3676       if (frame instanceof StructureViewerBase)
3677       {
3678         /*
3679          * Post jalview 2.4 schema includes structure view id
3680          */
3681         if (sviewid != null
3682                 && ((StructureViewerBase) frame).getViewId()
3683                         .equals(sviewid))
3684         {
3685           comp = (StructureViewerBase) frame;
3686           break; // break added in 2.9
3687         }
3688         /*
3689          * Otherwise test for matching position and size of viewer frame
3690          */
3691         else if (frame.getX() == svattrib.getX()
3692                 && frame.getY() == svattrib.getY()
3693                 && frame.getHeight() == svattrib.getHeight()
3694                 && frame.getWidth() == svattrib.getWidth())
3695         {
3696           comp = (StructureViewerBase) frame;
3697           // no break in faint hope of an exact match on viewId
3698         }
3699       }
3700     }
3701     return comp;
3702   }
3703
3704   /**
3705    * Link an AlignmentPanel to an existing structure viewer.
3706    * 
3707    * @param ap
3708    * @param viewer
3709    * @param oldFiles
3710    * @param useinViewerSuperpos
3711    * @param usetoColourbyseq
3712    * @param viewerColouring
3713    */
3714   protected void linkStructureViewer(AlignmentPanel ap,
3715           StructureViewerBase viewer, StructureViewerModel stateData)
3716   {
3717     // NOTE: if the jalview project is part of a shared session then
3718     // view synchronization should/could be done here.
3719
3720     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
3721     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
3722     final boolean viewerColouring = stateData.isColourByViewer();
3723     Map<File, StructureData> oldFiles = stateData.getFileData();
3724
3725     /*
3726      * Add mapping for sequences in this view to an already open viewer
3727      */
3728     final AAStructureBindingModel binding = viewer.getBinding();
3729     for (File id : oldFiles.keySet())
3730     {
3731       // add this and any other pdb files that should be present in the
3732       // viewer
3733       StructureData filedat = oldFiles.get(id);
3734       String pdbFile = filedat.getFilePath();
3735       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
3736       binding.getSsm().setMapping(seq, null, pdbFile,
3737               jalview.io.AppletFormatAdapter.FILE);
3738       binding.addSequenceForStructFile(pdbFile, seq);
3739     }
3740     // and add the AlignmentPanel's reference to the view panel
3741     viewer.addAlignmentPanel(ap);
3742     if (useinViewerSuperpos)
3743     {
3744       viewer.useAlignmentPanelForSuperposition(ap);
3745     }
3746     else
3747     {
3748       viewer.excludeAlignmentPanelForSuperposition(ap);
3749     }
3750     if (usetoColourbyseq)
3751     {
3752       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
3753     }
3754     else
3755     {
3756       viewer.excludeAlignmentPanelForColourbyseq(ap);
3757     }
3758   }
3759
3760   /**
3761    * Get all frames within the Desktop.
3762    * 
3763    * @return
3764    */
3765   protected JInternalFrame[] getAllFrames()
3766   {
3767     JInternalFrame[] frames = null;
3768     // TODO is this necessary - is it safe - risk of hanging?
3769     do
3770     {
3771       try
3772       {
3773         frames = Desktop.desktop.getAllFrames();
3774       } catch (ArrayIndexOutOfBoundsException e)
3775       {
3776         // occasional No such child exceptions are thrown here...
3777         try
3778         {
3779           Thread.sleep(10);
3780         } catch (InterruptedException f)
3781         {
3782         }
3783       }
3784     } while (frames == null);
3785     return frames;
3786   }
3787
3788   /**
3789    * 
3790    * @param supported
3791    *          - minimum version we are comparing against
3792    * @param version
3793    *          - version of data being processsed.
3794    * @return true if version is development/null or evaluates to the same or
3795    *         later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*)
3796    */
3797   private boolean isVersionStringLaterThan(String supported, String version)
3798   {
3799     if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD")
3800             || version.equalsIgnoreCase("Test")
3801             || version.equalsIgnoreCase("AUTOMATED BUILD"))
3802     {
3803       System.err.println("Assuming project file with "
3804               + (version == null ? "null" : version)
3805               + " is compatible with Jalview version " + supported);
3806       return true;
3807     }
3808     else
3809     {
3810       StringTokenizer currentV = new StringTokenizer(supported, "."), fileV = new StringTokenizer(
3811               version, ".");
3812       while (currentV.hasMoreTokens() && fileV.hasMoreTokens())
3813       {
3814         // convert b to decimal to catch bugfix releases within a series
3815         String curT = currentV.nextToken().toLowerCase().replace('b', '.');
3816         String fileT = fileV.nextToken().toLowerCase().replace('b', '.');
3817         try
3818         {
3819           if (Float.valueOf(curT) > Float.valueOf(fileT))
3820           {
3821             // current version is newer than the version that wrote the file
3822             return false;
3823           }
3824         } catch (NumberFormatException nfe)
3825         {
3826           System.err
3827                   .println("** WARNING: Version comparison failed for tokens ("
3828                           + curT
3829                           + ") and ("
3830                           + fileT
3831                           + ")\n** Current: '"
3832                           + supported + "' and Version: '" + version + "'");
3833         }
3834       }
3835       if (currentV.hasMoreElements())
3836       {
3837         // fileV has no minor version but identical series to current
3838         return false;
3839       }
3840     }
3841     return true;
3842   }
3843
3844   Vector<JalviewStructureDisplayI> newStructureViewers = null;
3845
3846   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
3847   {
3848     if (newStructureViewers != null)
3849     {
3850       sview.getBinding().setFinishedLoadingFromArchive(false);
3851       newStructureViewers.add(sview);
3852     }
3853   }
3854
3855   protected void setLoadingFinishedForNewStructureViewers()
3856   {
3857     if (newStructureViewers != null)
3858     {
3859       for (JalviewStructureDisplayI sview : newStructureViewers)
3860       {
3861         sview.getBinding().setFinishedLoadingFromArchive(true);
3862       }
3863       newStructureViewers.clear();
3864       newStructureViewers = null;
3865     }
3866   }
3867
3868   AlignFrame loadViewport(String file, JSeq[] JSEQ,
3869           List<SequenceI> hiddenSeqs, Alignment al,
3870           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
3871           String viewId, List<JvAnnotRow> autoAlan)
3872   {
3873     AlignFrame af = null;
3874     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
3875             uniqueSeqSetId, viewId);
3876
3877     af.setFileName(file, "Jalview");
3878
3879     for (int i = 0; i < JSEQ.length; i++)
3880     {
3881       af.viewport.setSequenceColour(af.viewport.getAlignment()
3882               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
3883     }
3884
3885     af.viewport.setGatherViewsHere(view.getGatheredViews());
3886
3887     if (view.getSequenceSetId() != null)
3888     {
3889       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
3890
3891       af.viewport.setSequenceSetId(uniqueSeqSetId);
3892       if (av != null)
3893       {
3894         // propagate shared settings to this new view
3895         af.viewport.setHistoryList(av.getHistoryList());
3896         af.viewport.setRedoList(av.getRedoList());
3897       }
3898       else
3899       {
3900         viewportsAdded.put(uniqueSeqSetId, af.viewport);
3901       }
3902       // TODO: check if this method can be called repeatedly without
3903       // side-effects if alignpanel already registered.
3904       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
3905     }
3906     // apply Hidden regions to view.
3907     if (hiddenSeqs != null)
3908     {
3909       for (int s = 0; s < JSEQ.length; s++)
3910       {
3911         jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
3912
3913         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
3914         {
3915           hidden.addSequence(
3916                   al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
3917         }
3918         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
3919       }
3920
3921       // jalview.datamodel.SequenceI[] hseqs = new
3922       // jalview.datamodel.SequenceI[hiddenSeqs
3923       // .size()];
3924       //
3925       // for (int s = 0; s < hiddenSeqs.size(); s++)
3926       // {
3927       // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
3928       // }
3929
3930       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
3931               .size()]);
3932       af.viewport.hideSequence(hseqs);
3933
3934     }
3935     // recover view properties and display parameters
3936     if (view.getViewName() != null)
3937     {
3938       af.viewport.viewName = view.getViewName();
3939       af.setInitialTabVisible();
3940     }
3941     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
3942             view.getHeight());
3943
3944     af.viewport.setShowAnnotation(view.getShowAnnotation());
3945     af.viewport.setAbovePIDThreshold(view.getPidSelected());
3946
3947     af.viewport.setColourText(view.getShowColourText());
3948
3949     af.viewport.setConservationSelected(view.getConservationSelected());
3950     af.viewport.setShowJVSuffix(view.getShowFullId());
3951     af.viewport.setRightAlignIds(view.getRightAlignIds());
3952     af.viewport.setFont(
3953             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
3954                     .getFontSize()), true);
3955     // TODO: allow custom charWidth/Heights to be restored by updating them
3956     // after setting font - which means set above to false
3957     af.viewport.setRenderGaps(view.getRenderGaps());
3958     af.viewport.setWrapAlignment(view.getWrapAlignment());
3959     af.viewport.setShowAnnotation(view.getShowAnnotation());
3960
3961     af.viewport.setShowBoxes(view.getShowBoxes());
3962
3963     af.viewport.setShowText(view.getShowText());
3964
3965     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
3966     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
3967     af.viewport.setThresholdTextColour(view.getTextColThreshold());
3968     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
3969             .isShowUnconserved() : false);
3970     af.viewport.setStartRes(view.getStartRes());
3971     af.viewport.setStartSeq(view.getStartSeq());
3972     af.alignPanel.updateLayout();
3973     ColourSchemeI cs = null;
3974     // apply colourschemes
3975     if (view.getBgColour() != null)
3976     {
3977       if (view.getBgColour().startsWith("ucs"))
3978       {
3979         cs = getUserColourScheme(jms, view.getBgColour());
3980       }
3981       else if (view.getBgColour().startsWith("Annotation"))
3982       {
3983         AnnotationColours viewAnnColour = view.getAnnotationColours();
3984         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
3985
3986         // annpos
3987
3988       }
3989       else
3990       {
3991         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
3992       }
3993
3994       if (cs != null)
3995       {
3996         cs.setThreshold(view.getPidThreshold(), true);
3997         cs.setConsensus(af.viewport.getSequenceConsensusHash());
3998       }
3999     }
4000
4001     af.viewport.setGlobalColourScheme(cs);
4002     af.viewport.setColourAppliesToAllGroups(false);
4003
4004     if (view.getConservationSelected() && cs != null)
4005     {
4006       cs.setConservationInc(view.getConsThreshold());
4007     }
4008
4009     af.changeColour(cs);
4010
4011     af.viewport.setColourAppliesToAllGroups(true);
4012
4013     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4014
4015     if (view.hasCentreColumnLabels())
4016     {
4017       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4018     }
4019     if (view.hasIgnoreGapsinConsensus())
4020     {
4021       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4022               null);
4023     }
4024     if (view.hasFollowHighlight())
4025     {
4026       af.viewport.setFollowHighlight(view.getFollowHighlight());
4027     }
4028     if (view.hasFollowSelection())
4029     {
4030       af.viewport.followSelection = view.getFollowSelection();
4031     }
4032     if (view.hasShowConsensusHistogram())
4033     {
4034       af.viewport.setShowConsensusHistogram(view
4035               .getShowConsensusHistogram());
4036     }
4037     else
4038     {
4039       af.viewport.setShowConsensusHistogram(true);
4040     }
4041     if (view.hasShowSequenceLogo())
4042     {
4043       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4044     }
4045     else
4046     {
4047       af.viewport.setShowSequenceLogo(false);
4048     }
4049     if (view.hasNormaliseSequenceLogo())
4050     {
4051       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4052     }
4053     if (view.hasShowDbRefTooltip())
4054     {
4055       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4056     }
4057     if (view.hasShowNPfeatureTooltip())
4058     {
4059       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4060     }
4061     if (view.hasShowGroupConsensus())
4062     {
4063       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4064     }
4065     else
4066     {
4067       af.viewport.setShowGroupConsensus(false);
4068     }
4069     if (view.hasShowGroupConservation())
4070     {
4071       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4072     }
4073     else
4074     {
4075       af.viewport.setShowGroupConservation(false);
4076     }
4077
4078     // recover featre settings
4079     if (jms.getFeatureSettings() != null)
4080     {
4081       FeaturesDisplayed fdi;
4082       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4083       String[] renderOrder = new String[jms.getFeatureSettings()
4084               .getSettingCount()];
4085       Hashtable featureGroups = new Hashtable();
4086       Hashtable featureColours = new Hashtable();
4087       Hashtable featureOrder = new Hashtable();
4088
4089       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4090       {
4091         Setting setting = jms.getFeatureSettings().getSetting(fs);
4092         if (setting.hasMincolour())
4093         {
4094           GraduatedColor gc = setting.hasMin() ? new GraduatedColor(
4095                   new java.awt.Color(setting.getMincolour()),
4096                   new java.awt.Color(setting.getColour()),
4097                   setting.getMin(), setting.getMax()) : new GraduatedColor(
4098                   new java.awt.Color(setting.getMincolour()),
4099                   new java.awt.Color(setting.getColour()), 0, 1);
4100           if (setting.hasThreshold())
4101           {
4102             gc.setThresh(setting.getThreshold());
4103             gc.setThreshType(setting.getThreshstate());
4104           }
4105           gc.setAutoScaled(true); // default
4106           if (setting.hasAutoScale())
4107           {
4108             gc.setAutoScaled(setting.getAutoScale());
4109           }
4110           if (setting.hasColourByLabel())
4111           {
4112             gc.setColourByLabel(setting.getColourByLabel());
4113           }
4114           // and put in the feature colour table.
4115           featureColours.put(setting.getType(), gc);
4116         }
4117         else
4118         {
4119           featureColours.put(setting.getType(),
4120                   new java.awt.Color(setting.getColour()));
4121         }
4122         renderOrder[fs] = setting.getType();
4123         if (setting.hasOrder())
4124         {
4125           featureOrder.put(setting.getType(), setting.getOrder());
4126         }
4127         else
4128         {
4129           featureOrder.put(setting.getType(), new Float(fs
4130                   / jms.getFeatureSettings().getSettingCount()));
4131         }
4132         if (setting.getDisplay())
4133         {
4134           fdi.setVisible(setting.getType());
4135         }
4136       }
4137       Hashtable fgtable = new Hashtable();
4138       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4139       {
4140         Group grp = jms.getFeatureSettings().getGroup(gs);
4141         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4142       }
4143       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4144       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4145       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4146       FeatureRendererSettings frs = new FeatureRendererSettings(
4147               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4148       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4149               .transferSettings(frs);
4150
4151     }
4152
4153     if (view.getHiddenColumnsCount() > 0)
4154     {
4155       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4156       {
4157         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4158                 .getHiddenColumns(c).getEnd() // +1
4159                 );
4160       }
4161     }
4162     if (view.getCalcIdParam() != null)
4163     {
4164       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4165       {
4166         if (calcIdParam != null)
4167         {
4168           if (recoverCalcIdParam(calcIdParam, af.viewport))
4169           {
4170           }
4171           else
4172           {
4173             warn("Couldn't recover parameters for "
4174                     + calcIdParam.getCalcId());
4175           }
4176         }
4177       }
4178     }
4179     af.setMenusFromViewport(af.viewport);
4180     
4181     // TODO: we don't need to do this if the viewport is aready visible.
4182     /*
4183      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4184      * has a 'cdna/protein complement' view, in which case save it in order to
4185      * populate a SplitFrame once all views have been read in.
4186      */
4187     String complementaryViewId = view.getComplementId();
4188     if (complementaryViewId == null)
4189     {
4190       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4191               view.getHeight());
4192       // recompute any autoannotation
4193       af.alignPanel.updateAnnotation(false, true);
4194       reorderAutoannotation(af, al, autoAlan);
4195       af.alignPanel.alignmentChanged();
4196     }
4197     else
4198     {
4199       splitFrameCandidates.put(view, af);
4200     }
4201     return af;
4202   }
4203
4204   private ColourSchemeI constructAnnotationColour(
4205           AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
4206           JalviewModelSequence jms, boolean checkGroupAnnColour)
4207   {
4208     boolean propagateAnnColour = false;
4209     ColourSchemeI cs = null;
4210     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4211     if (checkGroupAnnColour && al.getGroups() != null
4212             && al.getGroups().size() > 0)
4213     {
4214       // pre 2.8.1 behaviour
4215       // check to see if we should transfer annotation colours
4216       propagateAnnColour = true;
4217       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4218       {
4219         if (sg.cs instanceof AnnotationColourGradient)
4220         {
4221           propagateAnnColour = false;
4222         }
4223       }
4224     }
4225     // int find annotation
4226     if (annAlignment.getAlignmentAnnotation() != null)
4227     {
4228       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4229       {
4230         if (annAlignment.getAlignmentAnnotation()[i].label
4231                 .equals(viewAnnColour.getAnnotation()))
4232         {
4233           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4234           {
4235             annAlignment.getAlignmentAnnotation()[i]
4236                     .setThreshold(new jalview.datamodel.GraphLine(
4237                             viewAnnColour.getThreshold(), "Threshold",
4238                             java.awt.Color.black)
4239
4240                     );
4241           }
4242
4243           if (viewAnnColour.getColourScheme().equals("None"))
4244           {
4245             cs = new AnnotationColourGradient(
4246                     annAlignment.getAlignmentAnnotation()[i],
4247                     new java.awt.Color(viewAnnColour.getMinColour()),
4248                     new java.awt.Color(viewAnnColour.getMaxColour()),
4249                     viewAnnColour.getAboveThreshold());
4250           }
4251           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4252           {
4253             cs = new AnnotationColourGradient(
4254                     annAlignment.getAlignmentAnnotation()[i],
4255                     getUserColourScheme(jms,
4256                             viewAnnColour.getColourScheme()),
4257                     viewAnnColour.getAboveThreshold());
4258           }
4259           else
4260           {
4261             cs = new AnnotationColourGradient(
4262                     annAlignment.getAlignmentAnnotation()[i],
4263                     ColourSchemeProperty.getColour(al,
4264                             viewAnnColour.getColourScheme()),
4265                     viewAnnColour.getAboveThreshold());
4266           }
4267           if (viewAnnColour.hasPerSequence())
4268           {
4269             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4270                     .isPerSequence());
4271           }
4272           if (viewAnnColour.hasPredefinedColours())
4273           {
4274             ((AnnotationColourGradient) cs)
4275                     .setPredefinedColours(viewAnnColour
4276                             .isPredefinedColours());
4277           }
4278           if (propagateAnnColour && al.getGroups() != null)
4279           {
4280             // Also use these settings for all the groups
4281             for (int g = 0; g < al.getGroups().size(); g++)
4282             {
4283               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4284
4285               if (sg.cs == null)
4286               {
4287                 continue;
4288               }
4289
4290               /*
4291                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4292                * new AnnotationColourGradient(
4293                * annAlignment.getAlignmentAnnotation()[i], new
4294                * java.awt.Color(viewAnnColour. getMinColour()), new
4295                * java.awt.Color(viewAnnColour. getMaxColour()),
4296                * viewAnnColour.getAboveThreshold()); } else
4297                */
4298               {
4299                 sg.cs = new AnnotationColourGradient(
4300                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4301                         viewAnnColour.getAboveThreshold());
4302                 if (cs instanceof AnnotationColourGradient)
4303                 {
4304                   if (viewAnnColour.hasPerSequence())
4305                   {
4306                     ((AnnotationColourGradient) cs)
4307                             .setSeqAssociated(viewAnnColour.isPerSequence());
4308                   }
4309                   if (viewAnnColour.hasPredefinedColours())
4310                   {
4311                     ((AnnotationColourGradient) cs)
4312                             .setPredefinedColours(viewAnnColour
4313                                     .isPredefinedColours());
4314                   }
4315                 }
4316               }
4317
4318             }
4319           }
4320
4321           break;
4322         }
4323
4324       }
4325     }
4326     return cs;
4327   }
4328
4329   private void reorderAutoannotation(AlignFrame af, Alignment al,
4330           List<JvAnnotRow> autoAlan)
4331   {
4332     // copy over visualization settings for autocalculated annotation in the
4333     // view
4334     if (al.getAlignmentAnnotation() != null)
4335     {
4336       /**
4337        * Kludge for magic autoannotation names (see JAL-811)
4338        */
4339       String[] magicNames = new String[]
4340       { "Consensus", "Quality", "Conservation" };
4341       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4342       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4343       for (String nm : magicNames)
4344       {
4345         visan.put(nm, nullAnnot);
4346       }
4347       for (JvAnnotRow auan : autoAlan)
4348       {
4349         visan.put(auan.template.label
4350                 + (auan.template.getCalcId() == null ? "" : "\t"
4351                         + auan.template.getCalcId()), auan);
4352       }
4353       int hSize = al.getAlignmentAnnotation().length;
4354       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4355       // work through any autoCalculated annotation already on the view
4356       // removing it if it should be placed in a different location on the
4357       // annotation panel.
4358       List<String> remains = new ArrayList<String>(visan.keySet());
4359       for (int h = 0; h < hSize; h++)
4360       {
4361         jalview.datamodel.AlignmentAnnotation jalan = al
4362                 .getAlignmentAnnotation()[h];
4363         if (jalan.autoCalculated)
4364         {
4365           String k;
4366           JvAnnotRow valan = visan.get(k = jalan.label);
4367           if (jalan.getCalcId() != null)
4368           {
4369             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4370           }
4371
4372           if (valan != null)
4373           {
4374             // delete the auto calculated row from the alignment
4375             al.deleteAnnotation(jalan, false);
4376             remains.remove(k);
4377             hSize--;
4378             h--;
4379             if (valan != nullAnnot)
4380             {
4381               if (jalan != valan.template)
4382               {
4383                 // newly created autoannotation row instance
4384                 // so keep a reference to the visible annotation row
4385                 // and copy over all relevant attributes
4386                 if (valan.template.graphHeight >= 0)
4387
4388                 {
4389                   jalan.graphHeight = valan.template.graphHeight;
4390                 }
4391                 jalan.visible = valan.template.visible;
4392               }
4393               reorder.add(new JvAnnotRow(valan.order, jalan));
4394             }
4395           }
4396         }
4397       }
4398       // Add any (possibly stale) autocalculated rows that were not appended to
4399       // the view during construction
4400       for (String other : remains)
4401       {
4402         JvAnnotRow othera = visan.get(other);
4403         if (othera != nullAnnot && othera.template.getCalcId() != null
4404                 && othera.template.getCalcId().length() > 0)
4405         {
4406           reorder.add(othera);
4407         }
4408       }
4409       // now put the automatic annotation in its correct place
4410       int s = 0, srt[] = new int[reorder.size()];
4411       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4412       for (JvAnnotRow jvar : reorder)
4413       {
4414         rws[s] = jvar;
4415         srt[s++] = jvar.order;
4416       }
4417       reorder.clear();
4418       jalview.util.QuickSort.sort(srt, rws);
4419       // and re-insert the annotation at its correct position
4420       for (JvAnnotRow jvar : rws)
4421       {
4422         al.addAnnotation(jvar.template, jvar.order);
4423       }
4424       af.alignPanel.adjustAnnotationHeight();
4425     }
4426   }
4427
4428   Hashtable skipList = null;
4429
4430   /**
4431    * TODO remove this method
4432    * 
4433    * @param view
4434    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4435    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4436    *         throw new Error("Implementation Error. No skipList defined for this
4437    *         Jalview2XML instance."); } return (AlignFrame)
4438    *         skipList.get(view.getSequenceSetId()); }
4439    */
4440
4441   /**
4442    * Check if the Jalview view contained in object should be skipped or not.
4443    * 
4444    * @param object
4445    * @return true if view's sequenceSetId is a key in skipList
4446    */
4447   private boolean skipViewport(JalviewModel object)
4448   {
4449     if (skipList == null)
4450     {
4451       return false;
4452     }
4453     String id;
4454     if (skipList.containsKey(id = object.getJalviewModelSequence()
4455             .getViewport()[0].getSequenceSetId()))
4456     {
4457       if (Cache.log != null && Cache.log.isDebugEnabled())
4458       {
4459         Cache.log.debug("Skipping seuqence set id " + id);
4460       }
4461       return true;
4462     }
4463     return false;
4464   }
4465
4466   public void addToSkipList(AlignFrame af)
4467   {
4468     if (skipList == null)
4469     {
4470       skipList = new Hashtable();
4471     }
4472     skipList.put(af.getViewport().getSequenceSetId(), af);
4473   }
4474
4475   public void clearSkipList()
4476   {
4477     if (skipList != null)
4478     {
4479       skipList.clear();
4480       skipList = null;
4481     }
4482   }
4483
4484   private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
4485           boolean ignoreUnrefed)
4486   {
4487     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
4488     Vector dseqs = null;
4489     if (ds == null)
4490     {
4491       // create a list of new dataset sequences
4492       dseqs = new Vector();
4493     }
4494     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4495     {
4496       Sequence vamsasSeq = vamsasSet.getSequence(i);
4497       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4498     }
4499     // create a new dataset
4500     if (ds == null)
4501     {
4502       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4503       dseqs.copyInto(dsseqs);
4504       ds = new jalview.datamodel.Alignment(dsseqs);
4505       debug("Created new dataset " + vamsasSet.getDatasetId()
4506               + " for alignment " + System.identityHashCode(al));
4507       addDatasetRef(vamsasSet.getDatasetId(), ds);
4508     }
4509     // set the dataset for the newly imported alignment.
4510     if (al.getDataset() == null && !ignoreUnrefed)
4511     {
4512       al.setDataset(ds);
4513     }
4514   }
4515
4516   /**
4517    * 
4518    * @param vamsasSeq
4519    *          sequence definition to create/merge dataset sequence for
4520    * @param ds
4521    *          dataset alignment
4522    * @param dseqs
4523    *          vector to add new dataset sequence to
4524    */
4525   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4526           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4527   {
4528     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4529     // xRef Codon Maps
4530     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4531     SequenceI dsq = null;
4532     if (sq != null && sq.getDatasetSequence() != null)
4533     {
4534       dsq = sq.getDatasetSequence();
4535     }
4536     if (sq == null && ignoreUnrefed)
4537     {
4538       return;
4539     }
4540     String sqid = vamsasSeq.getDsseqid();
4541     if (dsq == null)
4542     {
4543       // need to create or add a new dataset sequence reference to this sequence
4544       if (sqid != null)
4545       {
4546         dsq = seqRefIds.get(sqid);
4547       }
4548       // check again
4549       if (dsq == null)
4550       {
4551         // make a new dataset sequence
4552         dsq = sq.createDatasetSequence();
4553         if (sqid == null)
4554         {
4555           // make up a new dataset reference for this sequence
4556           sqid = seqHash(dsq);
4557         }
4558         dsq.setVamsasId(uniqueSetSuffix + sqid);
4559         seqRefIds.put(sqid, dsq);
4560         if (ds == null)
4561         {
4562           if (dseqs != null)
4563           {
4564             dseqs.addElement(dsq);
4565           }
4566         }
4567         else
4568         {
4569           ds.addSequence(dsq);
4570         }
4571       }
4572       else
4573       {
4574         if (sq != dsq)
4575         { // make this dataset sequence sq's dataset sequence
4576           sq.setDatasetSequence(dsq);
4577           // and update the current dataset alignment
4578           if (ds == null)
4579           {
4580             if (dseqs != null)
4581             {
4582               if (!dseqs.contains(dsq))
4583               {
4584                 dseqs.add(dsq);
4585               }
4586             }
4587             else
4588             {
4589               if (ds.findIndex(dsq) < 0)
4590               {
4591                 ds.addSequence(dsq);
4592               }
4593             }
4594           }
4595         }
4596       }
4597     }
4598     // TODO: refactor this as a merge dataset sequence function
4599     // now check that sq (the dataset sequence) sequence really is the union of
4600     // all references to it
4601     // boolean pre = sq.getStart() < dsq.getStart();
4602     // boolean post = sq.getEnd() > dsq.getEnd();
4603     // if (pre || post)
4604     if (sq != dsq)
4605     {
4606       // StringBuffer sb = new StringBuffer();
4607       String newres = jalview.analysis.AlignSeq.extractGaps(
4608               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4609       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4610               && newres.length() > dsq.getLength())
4611       {
4612         // Update with the longer sequence.
4613         synchronized (dsq)
4614         {
4615           /*
4616            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4617            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4618            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4619            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4620            */
4621           dsq.setSequence(newres);
4622         }
4623         // TODO: merges will never happen if we 'know' we have the real dataset
4624         // sequence - this should be detected when id==dssid
4625         System.err
4626                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4627         // + (pre ? "prepended" : "") + " "
4628         // + (post ? "appended" : ""));
4629       }
4630     }
4631   }
4632
4633   java.util.Hashtable datasetIds = null;
4634
4635   java.util.IdentityHashMap dataset2Ids = null;
4636
4637   private Alignment getDatasetFor(String datasetId)
4638   {
4639     if (datasetIds == null)
4640     {
4641       datasetIds = new Hashtable();
4642       return null;
4643     }
4644     if (datasetIds.containsKey(datasetId))
4645     {
4646       return (Alignment) datasetIds.get(datasetId);
4647     }
4648     return null;
4649   }
4650
4651   private void addDatasetRef(String datasetId, Alignment dataset)
4652   {
4653     if (datasetIds == null)
4654     {
4655       datasetIds = new Hashtable();
4656     }
4657     datasetIds.put(datasetId, dataset);
4658   }
4659
4660   /**
4661    * make a new dataset ID for this jalview dataset alignment
4662    * 
4663    * @param dataset
4664    * @return
4665    */
4666   private String getDatasetIdRef(jalview.datamodel.Alignment dataset)
4667   {
4668     if (dataset.getDataset() != null)
4669     {
4670       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4671     }
4672     String datasetId = makeHashCode(dataset, null);
4673     if (datasetId == null)
4674     {
4675       // make a new datasetId and record it
4676       if (dataset2Ids == null)
4677       {
4678         dataset2Ids = new IdentityHashMap();
4679       }
4680       else
4681       {
4682         datasetId = (String) dataset2Ids.get(dataset);
4683       }
4684       if (datasetId == null)
4685       {
4686         datasetId = "ds" + dataset2Ids.size() + 1;
4687         dataset2Ids.put(dataset, datasetId);
4688       }
4689     }
4690     return datasetId;
4691   }
4692
4693   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4694   {
4695     for (int d = 0; d < sequence.getDBRefCount(); d++)
4696     {
4697       DBRef dr = sequence.getDBRef(d);
4698       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4699               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4700                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4701       if (dr.getMapping() != null)
4702       {
4703         entry.setMap(addMapping(dr.getMapping()));
4704       }
4705       datasetSequence.addDBRef(entry);
4706     }
4707   }
4708
4709   private jalview.datamodel.Mapping addMapping(Mapping m)
4710   {
4711     SequenceI dsto = null;
4712     // Mapping m = dr.getMapping();
4713     int fr[] = new int[m.getMapListFromCount() * 2];
4714     Enumeration f = m.enumerateMapListFrom();
4715     for (int _i = 0; f.hasMoreElements(); _i += 2)
4716     {
4717       MapListFrom mf = (MapListFrom) f.nextElement();
4718       fr[_i] = mf.getStart();
4719       fr[_i + 1] = mf.getEnd();
4720     }
4721     int fto[] = new int[m.getMapListToCount() * 2];
4722     f = m.enumerateMapListTo();
4723     for (int _i = 0; f.hasMoreElements(); _i += 2)
4724     {
4725       MapListTo mf = (MapListTo) f.nextElement();
4726       fto[_i] = mf.getStart();
4727       fto[_i + 1] = mf.getEnd();
4728     }
4729     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
4730             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
4731     if (m.getMappingChoice() != null)
4732     {
4733       MappingChoice mc = m.getMappingChoice();
4734       if (mc.getDseqFor() != null)
4735       {
4736         String dsfor = "" + mc.getDseqFor();
4737         if (seqRefIds.containsKey(dsfor))
4738         {
4739           /**
4740            * recover from hash
4741            */
4742           jmap.setTo(seqRefIds.get(dsfor));
4743         }
4744         else
4745         {
4746           frefedSequence.add(new Object[]
4747           { dsfor, jmap });
4748         }
4749       }
4750       else
4751       {
4752         /**
4753          * local sequence definition
4754          */
4755         Sequence ms = mc.getSequence();
4756         SequenceI djs = null;
4757         String sqid = ms.getDsseqid();
4758         if (sqid != null && sqid.length() > 0)
4759         {
4760           /*
4761            * recover dataset sequence
4762            */
4763           djs = seqRefIds.get(sqid);
4764         }
4765         else
4766         {
4767           System.err
4768                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
4769           sqid = ((Object) ms).toString(); // make up a new hascode for
4770           // undefined dataset sequence hash
4771           // (unlikely to happen)
4772         }
4773
4774         if (djs == null)
4775         {
4776           /**
4777            * make a new dataset sequence and add it to refIds hash
4778            */
4779           djs = new jalview.datamodel.Sequence(ms.getName(),
4780                   ms.getSequence());
4781           djs.setStart(jmap.getMap().getToLowest());
4782           djs.setEnd(jmap.getMap().getToHighest());
4783           djs.setVamsasId(uniqueSetSuffix + sqid);
4784           jmap.setTo(djs);
4785           seqRefIds.put(sqid, djs);
4786
4787         }
4788         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
4789         addDBRefs(djs, ms);
4790
4791       }
4792     }
4793     return (jmap);
4794
4795   }
4796
4797   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
4798           boolean keepSeqRefs)
4799   {
4800     initSeqRefs();
4801     jalview.schemabinding.version2.JalviewModel jm = saveState(ap, null,
4802             null);
4803
4804     if (!keepSeqRefs)
4805     {
4806       clearSeqRefs();
4807       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
4808     }
4809     else
4810     {
4811       uniqueSetSuffix = "";
4812       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
4813       // overwrite the
4814       // view we just
4815       // copied
4816     }
4817     if (this.frefedSequence == null)
4818     {
4819       frefedSequence = new Vector();
4820     }
4821
4822     viewportsAdded.clear();
4823
4824     AlignFrame af = loadFromObject(jm, null, false, null);
4825     af.alignPanels.clear();
4826     af.closeMenuItem_actionPerformed(true);
4827
4828     /*
4829      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
4830      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
4831      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
4832      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
4833      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
4834      */
4835
4836     return af.alignPanel;
4837   }
4838
4839   /**
4840    * flag indicating if hashtables should be cleared on finalization TODO this
4841    * flag may not be necessary
4842    */
4843   private final boolean _cleartables = true;
4844
4845   private Hashtable jvids2vobj;
4846
4847   /*
4848    * (non-Javadoc)
4849    * 
4850    * @see java.lang.Object#finalize()
4851    */
4852   @Override
4853   protected void finalize() throws Throwable
4854   {
4855     // really make sure we have no buried refs left.
4856     if (_cleartables)
4857     {
4858       clearSeqRefs();
4859     }
4860     this.seqRefIds = null;
4861     this.seqsToIds = null;
4862     super.finalize();
4863   }
4864
4865   private void warn(String msg)
4866   {
4867     warn(msg, null);
4868   }
4869
4870   private void warn(String msg, Exception e)
4871   {
4872     if (Cache.log != null)
4873     {
4874       if (e != null)
4875       {
4876         Cache.log.warn(msg, e);
4877       }
4878       else
4879       {
4880         Cache.log.warn(msg);
4881       }
4882     }
4883     else
4884     {
4885       System.err.println("Warning: " + msg);
4886       if (e != null)
4887       {
4888         e.printStackTrace();
4889       }
4890     }
4891   }
4892
4893   private void debug(String string)
4894   {
4895     debug(string, null);
4896   }
4897
4898   private void debug(String msg, Exception e)
4899   {
4900     if (Cache.log != null)
4901     {
4902       if (e != null)
4903       {
4904         Cache.log.debug(msg, e);
4905       }
4906       else
4907       {
4908         Cache.log.debug(msg);
4909       }
4910     }
4911     else
4912     {
4913       System.err.println("Warning: " + msg);
4914       if (e != null)
4915       {
4916         e.printStackTrace();
4917       }
4918     }
4919   }
4920
4921   /**
4922    * set the object to ID mapping tables used to write/recover objects and XML
4923    * ID strings for the jalview project. If external tables are provided then
4924    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
4925    * object goes out of scope. - also populates the datasetIds hashtable with
4926    * alignment objects containing dataset sequences
4927    * 
4928    * @param vobj2jv
4929    *          Map from ID strings to jalview datamodel
4930    * @param jv2vobj
4931    *          Map from jalview datamodel to ID strings
4932    * 
4933    * 
4934    */
4935   public void setObjectMappingTables(Hashtable vobj2jv,
4936           IdentityHashMap jv2vobj)
4937   {
4938     this.jv2vobj = jv2vobj;
4939     this.vobj2jv = vobj2jv;
4940     Iterator ds = jv2vobj.keySet().iterator();
4941     String id;
4942     while (ds.hasNext())
4943     {
4944       Object jvobj = ds.next();
4945       id = jv2vobj.get(jvobj).toString();
4946       if (jvobj instanceof jalview.datamodel.Alignment)
4947       {
4948         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
4949         {
4950           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
4951         }
4952       }
4953       else if (jvobj instanceof jalview.datamodel.Sequence)
4954       {
4955         // register sequence object so the XML parser can recover it.
4956         if (seqRefIds == null)
4957         {
4958           seqRefIds = new HashMap<String, SequenceI>();
4959         }
4960         if (seqsToIds == null)
4961         {
4962           seqsToIds = new IdentityHashMap<SequenceI, String>();
4963         }
4964         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
4965         seqsToIds.put((SequenceI) jvobj, id);
4966       }
4967       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
4968       {
4969         String anid;
4970         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
4971         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
4972         if (jvann.annotationId == null)
4973         {
4974           jvann.annotationId = anid;
4975         }
4976         if (!jvann.annotationId.equals(anid))
4977         {
4978           // TODO verify that this is the correct behaviour
4979           this.warn("Overriding Annotation ID for " + anid
4980                   + " from different id : " + jvann.annotationId);
4981           jvann.annotationId = anid;
4982         }
4983       }
4984       else if (jvobj instanceof String)
4985       {
4986         if (jvids2vobj == null)
4987         {
4988           jvids2vobj = new Hashtable();
4989           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
4990         }
4991       }
4992       else
4993       {
4994         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
4995       }
4996     }
4997   }
4998
4999   /**
5000    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5001    * objects created from the project archive. If string is null (default for
5002    * construction) then suffix will be set automatically.
5003    * 
5004    * @param string
5005    */
5006   public void setUniqueSetSuffix(String string)
5007   {
5008     uniqueSetSuffix = string;
5009
5010   }
5011
5012   /**
5013    * uses skipList2 as the skipList for skipping views on sequence sets
5014    * associated with keys in the skipList
5015    * 
5016    * @param skipList2
5017    */
5018   public void setSkipList(Hashtable skipList2)
5019   {
5020     skipList = skipList2;
5021   }
5022
5023   /**
5024    * Reads the jar entry of given name and returns its contents, or null if the
5025    * entry is not found.
5026    * 
5027    * @param jprovider
5028    * @param jarEntryName
5029    * @return
5030    */
5031   protected String readJarEntry(jarInputStreamProvider jprovider,
5032           String jarEntryName)
5033   {
5034     String result = null;
5035     BufferedReader in = null;
5036
5037     try
5038     {
5039       /*
5040        * Reopen the jar input stream and traverse its entries to find a matching
5041        * name
5042        */
5043       JarInputStream jin = jprovider.getJarInputStream();
5044       JarEntry entry = null;
5045       do
5046       {
5047         entry = jin.getNextJarEntry();
5048       } while (entry != null && !entry.getName().equals(jarEntryName));
5049
5050       if (entry != null)
5051       {
5052         StringBuilder out = new StringBuilder(256);
5053         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5054         String data;
5055
5056         while ((data = in.readLine()) != null)
5057         {
5058           out.append(data);
5059         }
5060         result = out.toString();
5061       }
5062       else
5063       {
5064         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5065       }
5066     } catch (Exception ex)
5067     {
5068       ex.printStackTrace();
5069     } finally
5070     {
5071       if (in != null)
5072       {
5073         try
5074         {
5075           in.close();
5076         } catch (IOException e)
5077         {
5078           // ignore
5079         }
5080       }
5081     }
5082   
5083     return result;
5084   }
5085 }