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