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