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