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