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