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