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