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