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