JAL-2089 patch broken merge to master for Release 2.10.0b1
[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             // jprovider is null when executing 'New View'
3021             if (ids[p].getFile() != null && jprovider != null)
3022             {
3023               if (!pdbloaded.containsKey(ids[p].getFile()))
3024               {
3025                 entry.setFile(loadPDBFile(jprovider, ids[p].getId(),
3026                         ids[p].getFile()));
3027               }
3028               else
3029               {
3030                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
3031               }
3032             }
3033             if (ids[p].getPdbentryItem() != null)
3034             {
3035               for (PdbentryItem item : ids[p].getPdbentryItem())
3036               {
3037                 for (Property pr : item.getProperty())
3038                 {
3039                   entry.setProperty(pr.getName(), pr.getValue());
3040                 }
3041               }
3042             }
3043             StructureSelectionManager.getStructureSelectionManager(
3044                     Desktop.instance).registerPDBEntry(entry);
3045             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3046             if (al.getSequenceAt(i).getDatasetSequence() != null)
3047             {
3048               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3049             }
3050             else
3051             {
3052               al.getSequenceAt(i).addPDBId(entry);
3053             }
3054           }
3055         }
3056       }
3057     } // end !multipleview
3058
3059     // ///////////////////////////////
3060     // LOAD SEQUENCE MAPPINGS
3061
3062     if (vamsasSet.getAlcodonFrameCount() > 0)
3063     {
3064       // TODO Potentially this should only be done once for all views of an
3065       // alignment
3066       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
3067       for (int i = 0; i < alc.length; i++)
3068       {
3069         AlignedCodonFrame cf = new AlignedCodonFrame();
3070         if (alc[i].getAlcodMapCount() > 0)
3071         {
3072           AlcodMap[] maps = alc[i].getAlcodMap();
3073           for (int m = 0; m < maps.length; m++)
3074           {
3075             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
3076             // Load Mapping
3077             jalview.datamodel.Mapping mapping = null;
3078             // attach to dna sequence reference.
3079             if (maps[m].getMapping() != null)
3080             {
3081               mapping = addMapping(maps[m].getMapping());
3082               if (dnaseq != null && mapping.getTo() != null)
3083               {
3084                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3085               }
3086               else
3087               {
3088                 // defer to later
3089                 frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
3090                         mapping));
3091               }
3092             }
3093           }
3094           al.addCodonFrame(cf);
3095         }
3096       }
3097     }
3098
3099     // ////////////////////////////////
3100     // LOAD ANNOTATIONS
3101     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
3102
3103     /*
3104      * store any annotations which forward reference a group's ID
3105      */
3106     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
3107
3108     if (vamsasSet.getAnnotationCount() > 0)
3109     {
3110       Annotation[] an = vamsasSet.getAnnotation();
3111
3112       for (int i = 0; i < an.length; i++)
3113       {
3114         Annotation annotation = an[i];
3115
3116         /**
3117          * test if annotation is automatically calculated for this view only
3118          */
3119         boolean autoForView = false;
3120         if (annotation.getLabel().equals("Quality")
3121                 || annotation.getLabel().equals("Conservation")
3122                 || annotation.getLabel().equals("Consensus"))
3123         {
3124           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3125           autoForView = true;
3126           if (!annotation.hasAutoCalculated())
3127           {
3128             annotation.setAutoCalculated(true);
3129           }
3130         }
3131         if (autoForView
3132                 || (annotation.hasAutoCalculated() && annotation
3133                         .isAutoCalculated()))
3134         {
3135           // remove ID - we don't recover annotation from other views for
3136           // view-specific annotation
3137           annotation.setId(null);
3138         }
3139
3140         // set visiblity for other annotation in this view
3141         String annotationId = annotation.getId();
3142         if (annotationId != null && annotationIds.containsKey(annotationId))
3143         {
3144           AlignmentAnnotation jda = annotationIds.get(annotationId);
3145           // in principle Visible should always be true for annotation displayed
3146           // in multiple views
3147           if (annotation.hasVisible())
3148           {
3149             jda.visible = annotation.getVisible();
3150           }
3151
3152           al.addAnnotation(jda);
3153
3154           continue;
3155         }
3156         // Construct new annotation from model.
3157         AnnotationElement[] ae = annotation.getAnnotationElement();
3158         jalview.datamodel.Annotation[] anot = null;
3159         java.awt.Color firstColour = null;
3160         int anpos;
3161         if (!annotation.getScoreOnly())
3162         {
3163           anot = new jalview.datamodel.Annotation[al.getWidth()];
3164           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
3165           {
3166             anpos = ae[aa].getPosition();
3167
3168             if (anpos >= anot.length)
3169             {
3170               continue;
3171             }
3172
3173             anot[anpos] = new jalview.datamodel.Annotation(
3174
3175             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
3176                     (ae[aa].getSecondaryStructure() == null || ae[aa]
3177                             .getSecondaryStructure().length() == 0) ? ' '
3178                             : ae[aa].getSecondaryStructure().charAt(0),
3179                     ae[aa].getValue()
3180
3181             );
3182             // JBPNote: Consider verifying dataflow for IO of secondary
3183             // structure annotation read from Stockholm files
3184             // this was added to try to ensure that
3185             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
3186             // {
3187             // anot[ae[aa].getPosition()].displayCharacter = "";
3188             // }
3189             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
3190             if (firstColour == null)
3191             {
3192               firstColour = anot[anpos].colour;
3193             }
3194           }
3195         }
3196         jalview.datamodel.AlignmentAnnotation jaa = null;
3197
3198         if (annotation.getGraph())
3199         {
3200           float llim = 0, hlim = 0;
3201           // if (autoForView || an[i].isAutoCalculated()) {
3202           // hlim=11f;
3203           // }
3204           jaa = new jalview.datamodel.AlignmentAnnotation(
3205                   annotation.getLabel(), annotation.getDescription(), anot,
3206                   llim, hlim, annotation.getGraphType());
3207
3208           jaa.graphGroup = annotation.getGraphGroup();
3209           jaa._linecolour = firstColour;
3210           if (annotation.getThresholdLine() != null)
3211           {
3212             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
3213                     .getThresholdLine().getValue(), annotation
3214                     .getThresholdLine().getLabel(), new java.awt.Color(
3215                     annotation.getThresholdLine().getColour())));
3216
3217           }
3218           if (autoForView || annotation.isAutoCalculated())
3219           {
3220             // Hardwire the symbol display line to ensure that labels for
3221             // histograms are displayed
3222             jaa.hasText = true;
3223           }
3224         }
3225         else
3226         {
3227           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
3228                   an[i].getDescription(), anot);
3229           jaa._linecolour = firstColour;
3230         }
3231         // register new annotation
3232         if (an[i].getId() != null)
3233         {
3234           annotationIds.put(an[i].getId(), jaa);
3235           jaa.annotationId = an[i].getId();
3236         }
3237         // recover sequence association
3238         String sequenceRef = an[i].getSequenceRef();
3239         if (sequenceRef != null)
3240         {
3241           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3242           SequenceI sequence = seqRefIds.get(sequenceRef);
3243           if (sequence == null)
3244           {
3245             // in pre-2.9 projects sequence ref is to sequence name
3246             sequence = al.findName(sequenceRef);
3247           }
3248           if (sequence != null)
3249           {
3250             jaa.createSequenceMapping(sequence, 1, true);
3251             sequence.addAlignmentAnnotation(jaa);
3252           }
3253         }
3254         // and make a note of any group association
3255         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3256         {
3257           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3258                   .get(an[i].getGroupRef());
3259           if (aal == null)
3260           {
3261             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
3262             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3263           }
3264           aal.add(jaa);
3265         }
3266
3267         if (an[i].hasScore())
3268         {
3269           jaa.setScore(an[i].getScore());
3270         }
3271         if (an[i].hasVisible())
3272         {
3273           jaa.visible = an[i].getVisible();
3274         }
3275
3276         if (an[i].hasCentreColLabels())
3277         {
3278           jaa.centreColLabels = an[i].getCentreColLabels();
3279         }
3280
3281         if (an[i].hasScaleColLabels())
3282         {
3283           jaa.scaleColLabel = an[i].getScaleColLabels();
3284         }
3285         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3286         {
3287           // newer files have an 'autoCalculated' flag and store calculation
3288           // state in viewport properties
3289           jaa.autoCalculated = true; // means annotation will be marked for
3290           // update at end of load.
3291         }
3292         if (an[i].hasGraphHeight())
3293         {
3294           jaa.graphHeight = an[i].getGraphHeight();
3295         }
3296         if (an[i].hasBelowAlignment())
3297         {
3298           jaa.belowAlignment = an[i].isBelowAlignment();
3299         }
3300         jaa.setCalcId(an[i].getCalcId());
3301         if (an[i].getPropertyCount() > 0)
3302         {
3303           for (jalview.schemabinding.version2.Property prop : an[i]
3304                   .getProperty())
3305           {
3306             jaa.setProperty(prop.getName(), prop.getValue());
3307           }
3308         }
3309         if (jaa.autoCalculated)
3310         {
3311           autoAlan.add(new JvAnnotRow(i, jaa));
3312         }
3313         else
3314         // if (!autoForView)
3315         {
3316           // add autocalculated group annotation and any user created annotation
3317           // for the view
3318           al.addAnnotation(jaa);
3319         }
3320       }
3321     }
3322     // ///////////////////////
3323     // LOAD GROUPS
3324     // Create alignment markup and styles for this view
3325     if (jms.getJGroupCount() > 0)
3326     {
3327       JGroup[] groups = jms.getJGroup();
3328       boolean addAnnotSchemeGroup = false;
3329       for (int i = 0; i < groups.length; i++)
3330       {
3331         JGroup jGroup = groups[i];
3332         ColourSchemeI cs = null;
3333         if (jGroup.getColour() != null)
3334         {
3335           if (jGroup.getColour().startsWith("ucs"))
3336           {
3337             cs = getUserColourScheme(jms, jGroup.getColour());
3338           }
3339           else if (jGroup.getColour().equals("AnnotationColourGradient")
3340                   && jGroup.getAnnotationColours() != null)
3341           {
3342             addAnnotSchemeGroup = true;
3343             cs = null;
3344           }
3345           else
3346           {
3347             cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
3348           }
3349
3350           if (cs != null)
3351           {
3352             cs.setThreshold(jGroup.getPidThreshold(), true);
3353           }
3354         }
3355
3356         Vector<SequenceI> seqs = new Vector<SequenceI>();
3357
3358         for (int s = 0; s < jGroup.getSeqCount(); s++)
3359         {
3360           String seqId = jGroup.getSeq(s) + "";
3361           SequenceI ts = seqRefIds.get(seqId);
3362
3363           if (ts != null)
3364           {
3365             seqs.addElement(ts);
3366           }
3367         }
3368
3369         if (seqs.size() < 1)
3370         {
3371           continue;
3372         }
3373
3374         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3375                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3376                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3377
3378         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3379
3380         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3381         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3382         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3383                 .isShowUnconserved() : false);
3384         sg.thresholdTextColour = jGroup.getTextColThreshold();
3385         if (jGroup.hasShowConsensusHistogram())
3386         {
3387           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3388         }
3389         ;
3390         if (jGroup.hasShowSequenceLogo())
3391         {
3392           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3393         }
3394         if (jGroup.hasNormaliseSequenceLogo())
3395         {
3396           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3397         }
3398         if (jGroup.hasIgnoreGapsinConsensus())
3399         {
3400           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3401         }
3402         if (jGroup.getConsThreshold() != 0)
3403         {
3404           Conservation c = new Conservation("All", 3,
3405                   sg.getSequences(null), 0, sg.getWidth() - 1);
3406           c.calculate();
3407           c.verdict(false, 25);
3408           sg.cs.setConservation(c);
3409         }
3410
3411         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3412         {
3413           // re-instate unique group/annotation row reference
3414           List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
3415                   .getId());
3416           if (jaal != null)
3417           {
3418             for (AlignmentAnnotation jaa : jaal)
3419             {
3420               jaa.groupRef = sg;
3421               if (jaa.autoCalculated)
3422               {
3423                 // match up and try to set group autocalc alignment row for this
3424                 // annotation
3425                 if (jaa.label.startsWith("Consensus for "))
3426                 {
3427                   sg.setConsensus(jaa);
3428                 }
3429                 // match up and try to set group autocalc alignment row for this
3430                 // annotation
3431                 if (jaa.label.startsWith("Conservation for "))
3432                 {
3433                   sg.setConservationRow(jaa);
3434                 }
3435               }
3436             }
3437           }
3438         }
3439         al.addGroup(sg);
3440         if (addAnnotSchemeGroup)
3441         {
3442           // reconstruct the annotation colourscheme
3443           sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(),
3444                   null, al, jms, false);
3445         }
3446       }
3447     }
3448     if (view == null)
3449     {
3450       // only dataset in this model, so just return.
3451       return null;
3452     }
3453     // ///////////////////////////////
3454     // LOAD VIEWPORT
3455
3456     // If we just load in the same jar file again, the sequenceSetId
3457     // will be the same, and we end up with multiple references
3458     // to the same sequenceSet. We must modify this id on load
3459     // so that each load of the file gives a unique id
3460     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3461     String viewId = (view.getId() == null ? null : view.getId()
3462             + uniqueSetSuffix);
3463     AlignFrame af = null;
3464     AlignViewport av = null;
3465     // now check to see if we really need to create a new viewport.
3466     if (multipleView && viewportsAdded.size() == 0)
3467     {
3468       // We recovered an alignment for which a viewport already exists.
3469       // TODO: fix up any settings necessary for overlaying stored state onto
3470       // state recovered from another document. (may not be necessary).
3471       // we may need a binding from a viewport in memory to one recovered from
3472       // XML.
3473       // and then recover its containing af to allow the settings to be applied.
3474       // TODO: fix for vamsas demo
3475       System.err
3476               .println("About to recover a viewport for existing alignment: Sequence set ID is "
3477                       + uniqueSeqSetId);
3478       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3479       if (seqsetobj != null)
3480       {
3481         if (seqsetobj instanceof String)
3482         {
3483           uniqueSeqSetId = (String) seqsetobj;
3484           System.err
3485                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3486                           + uniqueSeqSetId);
3487         }
3488         else
3489         {
3490           System.err
3491                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
3492         }
3493
3494       }
3495     }
3496     /**
3497      * indicate that annotation colours are applied across all groups (pre
3498      * Jalview 2.8.1 behaviour)
3499      */
3500     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan(
3501             "2.8.1", object.getVersion());
3502
3503     AlignmentPanel ap = null;
3504     boolean isnewview = true;
3505     if (viewId != null)
3506     {
3507       // Check to see if this alignment already has a view id == viewId
3508       jalview.gui.AlignmentPanel views[] = Desktop
3509               .getAlignmentPanels(uniqueSeqSetId);
3510       if (views != null && views.length > 0)
3511       {
3512         for (int v = 0; v < views.length; v++)
3513         {
3514           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3515           {
3516             // recover the existing alignpanel, alignframe, viewport
3517             af = views[v].alignFrame;
3518             av = views[v].av;
3519             ap = views[v];
3520             // TODO: could even skip resetting view settings if we don't want to
3521             // change the local settings from other jalview processes
3522             isnewview = false;
3523           }
3524         }
3525       }
3526     }
3527
3528     if (isnewview)
3529     {
3530       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3531               uniqueSeqSetId, viewId, autoAlan);
3532       av = af.viewport;
3533       ap = af.alignPanel;
3534     }
3535
3536     /*
3537      * Load any trees, PDB structures and viewers
3538      * 
3539      * Not done if flag is false (when this method is used for New View)
3540      */
3541     if (loadTreesAndStructures)
3542     {
3543       loadTrees(jms, view, af, av, ap);
3544       loadPDBStructures(jprovider, jseqs, af, ap);
3545       loadRnaViewers(jprovider, jseqs, ap);
3546     }
3547     // and finally return.
3548     return af;
3549   }
3550
3551   /**
3552    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3553    * panel is restored from separate jar entries, two (gapped and trimmed) per
3554    * sequence and secondary structure.
3555    * 
3556    * Currently each viewer shows just one sequence and structure (gapped and
3557    * trimmed), however this method is designed to support multiple sequences or
3558    * structures in viewers if wanted in future.
3559    * 
3560    * @param jprovider
3561    * @param jseqs
3562    * @param ap
3563    */
3564   private void loadRnaViewers(jarInputStreamProvider jprovider,
3565           JSeq[] jseqs, AlignmentPanel ap)
3566   {
3567     /*
3568      * scan the sequences for references to viewers; create each one the first
3569      * time it is referenced, add Rna models to existing viewers
3570      */
3571     for (JSeq jseq : jseqs)
3572     {
3573       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3574       {
3575         RnaViewer viewer = jseq.getRnaViewer(i);
3576         AppVarna appVarna = findOrCreateVarnaViewer(viewer,
3577                 uniqueSetSuffix, ap);
3578
3579         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3580         {
3581           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3582           SequenceI seq = seqRefIds.get(jseq.getId());
3583           AlignmentAnnotation ann = this.annotationIds.get(ss
3584                   .getAnnotationId());
3585
3586           /*
3587            * add the structure to the Varna display (with session state copied
3588            * from the jar to a temporary file)
3589            */
3590           boolean gapped = ss.isGapped();
3591           String rnaTitle = ss.getTitle();
3592           String sessionState = ss.getViewerState();
3593           String tempStateFile = copyJarEntry(jprovider, sessionState,
3594                   "varna", null);
3595           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3596           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3597         }
3598         appVarna.setInitialSelection(viewer.getSelectedRna());
3599       }
3600     }
3601   }
3602
3603   /**
3604    * Locate and return an already instantiated matching AppVarna, or create one
3605    * if not found
3606    * 
3607    * @param viewer
3608    * @param viewIdSuffix
3609    * @param ap
3610    * @return
3611    */
3612   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3613           String viewIdSuffix, AlignmentPanel ap)
3614   {
3615     /*
3616      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3617      * if load is repeated
3618      */
3619     String postLoadId = viewer.getViewId() + viewIdSuffix;
3620     for (JInternalFrame frame : getAllFrames())
3621     {
3622       if (frame instanceof AppVarna)
3623       {
3624         AppVarna varna = (AppVarna) frame;
3625         if (postLoadId.equals(varna.getViewId()))
3626         {
3627           // this viewer is already instantiated
3628           // could in future here add ap as another 'parent' of the
3629           // AppVarna window; currently just 1-to-many
3630           return varna;
3631         }
3632       }
3633     }
3634
3635     /*
3636      * viewer not found - make it
3637      */
3638     RnaViewerModel model = new RnaViewerModel(postLoadId,
3639             viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
3640             viewer.getWidth(), viewer.getHeight(),
3641             viewer.getDividerLocation());
3642     AppVarna varna = new AppVarna(model, ap);
3643
3644     return varna;
3645   }
3646
3647   /**
3648    * Load any saved trees
3649    * 
3650    * @param jms
3651    * @param view
3652    * @param af
3653    * @param av
3654    * @param ap
3655    */
3656   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3657           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3658   {
3659     // TODO result of automated refactoring - are all these parameters needed?
3660     try
3661     {
3662       for (int t = 0; t < jms.getTreeCount(); t++)
3663       {
3664
3665         Tree tree = jms.getTree(t);
3666
3667         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3668         if (tp == null)
3669         {
3670           tp = af.ShowNewickTree(
3671                   new jalview.io.NewickFile(tree.getNewick()),
3672                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3673                   tree.getXpos(), tree.getYpos());
3674           if (tree.getId() != null)
3675           {
3676             // perhaps bind the tree id to something ?
3677           }
3678         }
3679         else
3680         {
3681           // update local tree attributes ?
3682           // TODO: should check if tp has been manipulated by user - if so its
3683           // settings shouldn't be modified
3684           tp.setTitle(tree.getTitle());
3685           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3686                   .getWidth(), tree.getHeight()));
3687           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3688           // views'
3689           // works still
3690           tp.treeCanvas.av = av; // af.viewport;
3691           tp.treeCanvas.ap = ap; // af.alignPanel;
3692
3693         }
3694         if (tp == null)
3695         {
3696           warn("There was a problem recovering stored Newick tree: \n"
3697                   + tree.getNewick());
3698           continue;
3699         }
3700
3701         tp.fitToWindow.setState(tree.getFitToWindow());
3702         tp.fitToWindow_actionPerformed(null);
3703
3704         if (tree.getFontName() != null)
3705         {
3706           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3707                   .getFontStyle(), tree.getFontSize()));
3708         }
3709         else
3710         {
3711           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3712                   .getFontStyle(), tree.getFontSize()));
3713         }
3714
3715         tp.showPlaceholders(tree.getMarkUnlinked());
3716         tp.showBootstrap(tree.getShowBootstrap());
3717         tp.showDistances(tree.getShowDistances());
3718
3719         tp.treeCanvas.threshold = tree.getThreshold();
3720
3721         if (tree.getCurrentTree())
3722         {
3723           af.viewport.setCurrentTree(tp.getTree());
3724         }
3725       }
3726
3727     } catch (Exception ex)
3728     {
3729       ex.printStackTrace();
3730     }
3731   }
3732
3733   /**
3734    * Load and link any saved structure viewers.
3735    * 
3736    * @param jprovider
3737    * @param jseqs
3738    * @param af
3739    * @param ap
3740    */
3741   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3742           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3743   {
3744     /*
3745      * Run through all PDB ids on the alignment, and collect mappings between
3746      * distinct view ids and all sequences referring to that view.
3747      */
3748     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3749
3750     for (int i = 0; i < jseqs.length; i++)
3751     {
3752       if (jseqs[i].getPdbidsCount() > 0)
3753       {
3754         Pdbids[] ids = jseqs[i].getPdbids();
3755         for (int p = 0; p < ids.length; p++)
3756         {
3757           final int structureStateCount = ids[p].getStructureStateCount();
3758           for (int s = 0; s < structureStateCount; s++)
3759           {
3760             // check to see if we haven't already created this structure view
3761             final StructureState structureState = ids[p]
3762                     .getStructureState(s);
3763             String sviewid = (structureState.getViewId() == null) ? null
3764                     : structureState.getViewId() + uniqueSetSuffix;
3765             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3766             // Originally : ids[p].getFile()
3767             // : TODO: verify external PDB file recovery still works in normal
3768             // jalview project load
3769             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId(),
3770                     ids[p].getFile()));
3771             jpdb.setId(ids[p].getId());
3772
3773             int x = structureState.getXpos();
3774             int y = structureState.getYpos();
3775             int width = structureState.getWidth();
3776             int height = structureState.getHeight();
3777
3778             // Probably don't need to do this anymore...
3779             // Desktop.desktop.getComponentAt(x, y);
3780             // TODO: NOW: check that this recovers the PDB file correctly.
3781             String pdbFile = loadPDBFile(jprovider, ids[p].getId(),
3782                     ids[p].getFile());
3783             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3784                     .getId() + "");
3785             if (sviewid == null)
3786             {
3787               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3788                       + "," + height;
3789             }
3790             if (!structureViewers.containsKey(sviewid))
3791             {
3792               structureViewers.put(sviewid,
3793                       new StructureViewerModel(x, y, width, height, false,
3794                               false, true, structureState.getViewId(),
3795                               structureState.getType()));
3796               // Legacy pre-2.7 conversion JAL-823 :
3797               // do not assume any view has to be linked for colour by
3798               // sequence
3799             }
3800
3801             // assemble String[] { pdb files }, String[] { id for each
3802             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3803             // seqs_file 2}, boolean[] {
3804             // linkAlignPanel,superposeWithAlignpanel}} from hash
3805             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3806             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3807                     | (structureState.hasAlignwithAlignPanel() ? structureState
3808                             .getAlignwithAlignPanel() : false));
3809
3810             /*
3811              * Default colour by linked panel to false if not specified (e.g.
3812              * for pre-2.7 projects)
3813              */
3814             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3815             colourWithAlignPanel |= (structureState
3816                     .hasColourwithAlignPanel() ? structureState
3817                     .getColourwithAlignPanel() : false);
3818             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3819
3820             /*
3821              * Default colour by viewer to true if not specified (e.g. for
3822              * pre-2.7 projects)
3823              */
3824             boolean colourByViewer = jmoldat.isColourByViewer();
3825             colourByViewer &= structureState.hasColourByJmol() ? structureState
3826                     .getColourByJmol() : true;
3827             jmoldat.setColourByViewer(colourByViewer);
3828
3829             if (jmoldat.getStateData().length() < structureState
3830                     .getContent().length())
3831             {
3832               {
3833                 jmoldat.setStateData(structureState.getContent());
3834               }
3835             }
3836             if (ids[p].getFile() != null)
3837             {
3838               File mapkey = new File(ids[p].getFile());
3839               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3840               if (seqstrmaps == null)
3841               {
3842                 jmoldat.getFileData().put(
3843                         mapkey,
3844                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3845                                 ids[p].getId()));
3846               }
3847               if (!seqstrmaps.getSeqList().contains(seq))
3848               {
3849                 seqstrmaps.getSeqList().add(seq);
3850                 // TODO and chains?
3851               }
3852             }
3853             else
3854             {
3855               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");
3856               warn(errorMessage);
3857             }
3858           }
3859         }
3860       }
3861     }
3862     // Instantiate the associated structure views
3863     for (Entry<String, StructureViewerModel> entry : structureViewers
3864             .entrySet())
3865     {
3866       try
3867       {
3868         createOrLinkStructureViewer(entry, af, ap, jprovider);
3869       } catch (Exception e)
3870       {
3871         System.err.println("Error loading structure viewer: "
3872                 + e.getMessage());
3873         // failed - try the next one
3874       }
3875     }
3876   }
3877
3878   /**
3879    * 
3880    * @param viewerData
3881    * @param af
3882    * @param ap
3883    * @param jprovider
3884    */
3885   protected void createOrLinkStructureViewer(
3886           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3887           AlignmentPanel ap, jarInputStreamProvider jprovider)
3888   {
3889     final StructureViewerModel stateData = viewerData.getValue();
3890
3891     /*
3892      * Search for any viewer windows already open from other alignment views
3893      * that exactly match the stored structure state
3894      */
3895     StructureViewerBase comp = findMatchingViewer(viewerData);
3896
3897     if (comp != null)
3898     {
3899       linkStructureViewer(ap, comp, stateData);
3900       return;
3901     }
3902
3903     /*
3904      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3905      * "viewer_"+stateData.viewId
3906      */
3907     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3908     {
3909       createChimeraViewer(viewerData, af, jprovider);
3910     }
3911     else
3912     {
3913       /*
3914        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3915        */
3916       createJmolViewer(viewerData, af, jprovider);
3917     }
3918   }
3919
3920   /**
3921    * Create a new Chimera viewer.
3922    * 
3923    * @param data
3924    * @param af
3925    * @param jprovider
3926    */
3927   protected void createChimeraViewer(
3928           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3929           jarInputStreamProvider jprovider)
3930   {
3931     StructureViewerModel data = viewerData.getValue();
3932     String chimeraSessionFile = data.getStateData();
3933
3934     /*
3935      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3936      * 
3937      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3938      * 'uniquified' sviewid used to reconstruct the viewer here
3939      */
3940     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3941     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3942             "chimera", null);
3943
3944     Set<Entry<File, StructureData>> fileData = data.getFileData()
3945             .entrySet();
3946     List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3947     List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3948     for (Entry<File, StructureData> pdb : fileData)
3949     {
3950       String filePath = pdb.getValue().getFilePath();
3951       String pdbId = pdb.getValue().getPdbId();
3952       // pdbs.add(new PDBEntry(filePath, pdbId));
3953       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3954       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3955       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3956       allseqs.add(seqs);
3957     }
3958
3959     boolean colourByChimera = data.isColourByViewer();
3960     boolean colourBySequence = data.isColourWithAlignPanel();
3961
3962     // TODO use StructureViewer as a factory here, see JAL-1761
3963     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3964     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3965             .size()][]);
3966     String newViewId = viewerData.getKey();
3967
3968     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3969             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3970             colourBySequence, newViewId);
3971     cvf.setSize(data.getWidth(), data.getHeight());
3972     cvf.setLocation(data.getX(), data.getY());
3973   }
3974
3975   /**
3976    * Create a new Jmol window. First parse the Jmol state to translate filenames
3977    * loaded into the view, and record the order in which files are shown in the
3978    * Jmol view, so we can add the sequence mappings in same order.
3979    * 
3980    * @param viewerData
3981    * @param af
3982    * @param jprovider
3983    */
3984   protected void createJmolViewer(
3985           final Entry<String, StructureViewerModel> viewerData,
3986           AlignFrame af, jarInputStreamProvider jprovider)
3987   {
3988     final StructureViewerModel svattrib = viewerData.getValue();
3989     String state = svattrib.getStateData();
3990
3991     /*
3992      * Pre-2.9: state element value is the Jmol state string
3993      * 
3994      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3995      * + viewId
3996      */
3997     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3998     {
3999       state = readJarEntry(jprovider,
4000               getViewerJarEntryName(svattrib.getViewId()));
4001     }
4002
4003     List<String> pdbfilenames = new ArrayList<String>();
4004     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
4005     List<String> pdbids = new ArrayList<String>();
4006     StringBuilder newFileLoc = new StringBuilder(64);
4007     int cp = 0, ncp, ecp;
4008     Map<File, StructureData> oldFiles = svattrib.getFileData();
4009     while ((ncp = state.indexOf("load ", cp)) > -1)
4010     {
4011       do
4012       {
4013         // look for next filename in load statement
4014         newFileLoc.append(state.substring(cp,
4015                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4016         String oldfilenam = state.substring(ncp,
4017                 ecp = state.indexOf("\"", ncp));
4018         // recover the new mapping data for this old filename
4019         // have to normalize filename - since Jmol and jalview do
4020         // filename
4021         // translation differently.
4022         StructureData filedat = oldFiles.get(new File(oldfilenam));
4023         if (filedat == null)
4024         {
4025           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4026           filedat = oldFiles.get(new File(reformatedOldFilename));
4027         }
4028         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4029         pdbfilenames.add(filedat.getFilePath());
4030         pdbids.add(filedat.getPdbId());
4031         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4032         newFileLoc.append("\"");
4033         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4034                       // look for next file statement.
4035       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4036     }
4037     if (cp > 0)
4038     {
4039       // just append rest of state
4040       newFileLoc.append(state.substring(cp));
4041     }
4042     else
4043     {
4044       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4045       newFileLoc = new StringBuilder(state);
4046       newFileLoc.append("; load append ");
4047       for (File id : oldFiles.keySet())
4048       {
4049         // add this and any other pdb files that should be present in
4050         // the viewer
4051         StructureData filedat = oldFiles.get(id);
4052         newFileLoc.append(filedat.getFilePath());
4053         pdbfilenames.add(filedat.getFilePath());
4054         pdbids.add(filedat.getPdbId());
4055         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4056         newFileLoc.append(" \"");
4057         newFileLoc.append(filedat.getFilePath());
4058         newFileLoc.append("\"");
4059
4060       }
4061       newFileLoc.append(";");
4062     }
4063
4064     if (newFileLoc.length() == 0)
4065     {
4066       return;
4067     }
4068     int histbug = newFileLoc.indexOf("history = ");
4069     if (histbug > -1)
4070     {
4071       /*
4072        * change "history = [true|false];" to "history = [1|0];"
4073        */
4074       histbug += 10;
4075       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4076       String val = (diff == -1) ? null : newFileLoc
4077               .substring(histbug, diff);
4078       if (val != null && val.length() >= 4)
4079       {
4080         if (val.contains("e")) // eh? what can it be?
4081         {
4082           if (val.trim().equals("true"))
4083           {
4084             val = "1";
4085           }
4086           else
4087           {
4088             val = "0";
4089           }
4090           newFileLoc.replace(histbug, diff, val);
4091         }
4092       }
4093     }
4094
4095     final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
4096             .size()]);
4097     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4098     final SequenceI[][] sq = seqmaps
4099             .toArray(new SequenceI[seqmaps.size()][]);
4100     final String fileloc = newFileLoc.toString();
4101     final String sviewid = viewerData.getKey();
4102     final AlignFrame alf = af;
4103     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4104             svattrib.getWidth(), svattrib.getHeight());
4105     try
4106     {
4107       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4108       {
4109         @Override
4110         public void run()
4111         {
4112           JalviewStructureDisplayI sview = null;
4113           try
4114           {
4115             sview = new StructureViewer(alf.alignPanel
4116                     .getStructureSelectionManager()).createView(
4117                     StructureViewer.ViewerType.JMOL, pdbf, id, sq,
4118                     alf.alignPanel, svattrib, fileloc, rect, sviewid);
4119             addNewStructureViewer(sview);
4120           } catch (OutOfMemoryError ex)
4121           {
4122             new OOMWarning("restoring structure view for PDB id " + id,
4123                     (OutOfMemoryError) ex.getCause());
4124             if (sview != null && sview.isVisible())
4125             {
4126               sview.closeViewer(false);
4127               sview.setVisible(false);
4128               sview.dispose();
4129             }
4130           }
4131         }
4132       });
4133     } catch (InvocationTargetException ex)
4134     {
4135       warn("Unexpected error when opening Jmol view.", ex);
4136
4137     } catch (InterruptedException e)
4138     {
4139       // e.printStackTrace();
4140     }
4141
4142   }
4143
4144   /**
4145    * Generates a name for the entry in the project jar file to hold state
4146    * information for a structure viewer
4147    * 
4148    * @param viewId
4149    * @return
4150    */
4151   protected String getViewerJarEntryName(String viewId)
4152   {
4153     return VIEWER_PREFIX + viewId;
4154   }
4155
4156   /**
4157    * Returns any open frame that matches given structure viewer data. The match
4158    * is based on the unique viewId, or (for older project versions) the frame's
4159    * geometry.
4160    * 
4161    * @param viewerData
4162    * @return
4163    */
4164   protected StructureViewerBase findMatchingViewer(
4165           Entry<String, StructureViewerModel> viewerData)
4166   {
4167     final String sviewid = viewerData.getKey();
4168     final StructureViewerModel svattrib = viewerData.getValue();
4169     StructureViewerBase comp = null;
4170     JInternalFrame[] frames = getAllFrames();
4171     for (JInternalFrame frame : frames)
4172     {
4173       if (frame instanceof StructureViewerBase)
4174       {
4175         /*
4176          * Post jalview 2.4 schema includes structure view id
4177          */
4178         if (sviewid != null
4179                 && ((StructureViewerBase) frame).getViewId()
4180                         .equals(sviewid))
4181         {
4182           comp = (StructureViewerBase) frame;
4183           break; // break added in 2.9
4184         }
4185         /*
4186          * Otherwise test for matching position and size of viewer frame
4187          */
4188         else if (frame.getX() == svattrib.getX()
4189                 && frame.getY() == svattrib.getY()
4190                 && frame.getHeight() == svattrib.getHeight()
4191                 && frame.getWidth() == svattrib.getWidth())
4192         {
4193           comp = (StructureViewerBase) frame;
4194           // no break in faint hope of an exact match on viewId
4195         }
4196       }
4197     }
4198     return comp;
4199   }
4200
4201   /**
4202    * Link an AlignmentPanel to an existing structure viewer.
4203    * 
4204    * @param ap
4205    * @param viewer
4206    * @param oldFiles
4207    * @param useinViewerSuperpos
4208    * @param usetoColourbyseq
4209    * @param viewerColouring
4210    */
4211   protected void linkStructureViewer(AlignmentPanel ap,
4212           StructureViewerBase viewer, StructureViewerModel stateData)
4213   {
4214     // NOTE: if the jalview project is part of a shared session then
4215     // view synchronization should/could be done here.
4216
4217     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4218     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4219     final boolean viewerColouring = stateData.isColourByViewer();
4220     Map<File, StructureData> oldFiles = stateData.getFileData();
4221
4222     /*
4223      * Add mapping for sequences in this view to an already open viewer
4224      */
4225     final AAStructureBindingModel binding = viewer.getBinding();
4226     for (File id : oldFiles.keySet())
4227     {
4228       // add this and any other pdb files that should be present in the
4229       // viewer
4230       StructureData filedat = oldFiles.get(id);
4231       String pdbFile = filedat.getFilePath();
4232       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4233       binding.getSsm().setMapping(seq, null, pdbFile,
4234               jalview.io.AppletFormatAdapter.FILE);
4235       binding.addSequenceForStructFile(pdbFile, seq);
4236     }
4237     // and add the AlignmentPanel's reference to the view panel
4238     viewer.addAlignmentPanel(ap);
4239     if (useinViewerSuperpos)
4240     {
4241       viewer.useAlignmentPanelForSuperposition(ap);
4242     }
4243     else
4244     {
4245       viewer.excludeAlignmentPanelForSuperposition(ap);
4246     }
4247     if (usetoColourbyseq)
4248     {
4249       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4250     }
4251     else
4252     {
4253       viewer.excludeAlignmentPanelForColourbyseq(ap);
4254     }
4255   }
4256
4257   /**
4258    * Get all frames within the Desktop.
4259    * 
4260    * @return
4261    */
4262   protected JInternalFrame[] getAllFrames()
4263   {
4264     JInternalFrame[] frames = null;
4265     // TODO is this necessary - is it safe - risk of hanging?
4266     do
4267     {
4268       try
4269       {
4270         frames = Desktop.desktop.getAllFrames();
4271       } catch (ArrayIndexOutOfBoundsException e)
4272       {
4273         // occasional No such child exceptions are thrown here...
4274         try
4275         {
4276           Thread.sleep(10);
4277         } catch (InterruptedException f)
4278         {
4279         }
4280       }
4281     } while (frames == null);
4282     return frames;
4283   }
4284
4285   /**
4286    * Answers true if 'version' is equal to or later than 'supported', where each
4287    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4288    * changes. Development and test values for 'version' are leniently treated
4289    * i.e. answer true.
4290    * 
4291    * @param supported
4292    *          - minimum version we are comparing against
4293    * @param version
4294    *          - version of data being processsed
4295    * @return
4296    */
4297   public static boolean isVersionStringLaterThan(String supported,
4298           String version)
4299   {
4300     if (supported == null || version == null
4301             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4302             || version.equalsIgnoreCase("Test")
4303             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4304     {
4305       System.err.println("Assuming project file with "
4306               + (version == null ? "null" : version)
4307               + " is compatible with Jalview version " + supported);
4308       return true;
4309     }
4310     else
4311     {
4312       return StringUtils.compareVersions(version, supported, "b") >= 0;
4313     }
4314   }
4315
4316   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4317
4318   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4319   {
4320     if (newStructureViewers != null)
4321     {
4322       sview.getBinding().setFinishedLoadingFromArchive(false);
4323       newStructureViewers.add(sview);
4324     }
4325   }
4326
4327   protected void setLoadingFinishedForNewStructureViewers()
4328   {
4329     if (newStructureViewers != null)
4330     {
4331       for (JalviewStructureDisplayI sview : newStructureViewers)
4332       {
4333         sview.getBinding().setFinishedLoadingFromArchive(true);
4334       }
4335       newStructureViewers.clear();
4336       newStructureViewers = null;
4337     }
4338   }
4339
4340   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4341           List<SequenceI> hiddenSeqs, AlignmentI al,
4342           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4343           String viewId, List<JvAnnotRow> autoAlan)
4344   {
4345     AlignFrame af = null;
4346     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4347             uniqueSeqSetId, viewId);
4348
4349     af.setFileName(file, "Jalview");
4350
4351     for (int i = 0; i < JSEQ.length; i++)
4352     {
4353       af.viewport.setSequenceColour(af.viewport.getAlignment()
4354               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4355     }
4356
4357     if (al.hasSeqrep())
4358     {
4359       af.getViewport().setColourByReferenceSeq(true);
4360       af.getViewport().setDisplayReferenceSeq(true);
4361     }
4362
4363     af.viewport.setGatherViewsHere(view.getGatheredViews());
4364
4365     if (view.getSequenceSetId() != null)
4366     {
4367       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4368
4369       af.viewport.setSequenceSetId(uniqueSeqSetId);
4370       if (av != null)
4371       {
4372         // propagate shared settings to this new view
4373         af.viewport.setHistoryList(av.getHistoryList());
4374         af.viewport.setRedoList(av.getRedoList());
4375       }
4376       else
4377       {
4378         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4379       }
4380       // TODO: check if this method can be called repeatedly without
4381       // side-effects if alignpanel already registered.
4382       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4383     }
4384     // apply Hidden regions to view.
4385     if (hiddenSeqs != null)
4386     {
4387       for (int s = 0; s < JSEQ.length; s++)
4388       {
4389         SequenceGroup hidden = new SequenceGroup();
4390         boolean isRepresentative = false;
4391         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4392         {
4393           isRepresentative = true;
4394           SequenceI sequenceToHide = al.getSequenceAt(JSEQ[s]
4395                   .getHiddenSequences(r));
4396           hidden.addSequence(sequenceToHide, false);
4397           // remove from hiddenSeqs list so we don't try to hide it twice
4398           hiddenSeqs.remove(sequenceToHide);
4399         }
4400         if (isRepresentative)
4401         {
4402           SequenceI representativeSequence = al.getSequenceAt(s);
4403           hidden.addSequence(representativeSequence, false);
4404           af.viewport.hideRepSequences(representativeSequence, hidden);
4405         }
4406       }
4407
4408       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4409               .size()]);
4410       af.viewport.hideSequence(hseqs);
4411
4412     }
4413     // recover view properties and display parameters
4414     if (view.getViewName() != null)
4415     {
4416       af.viewport.viewName = view.getViewName();
4417       af.setInitialTabVisible();
4418     }
4419     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4420             view.getHeight());
4421
4422     af.viewport.setShowAnnotation(view.getShowAnnotation());
4423     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4424
4425     af.viewport.setColourText(view.getShowColourText());
4426
4427     af.viewport.setConservationSelected(view.getConservationSelected());
4428     af.viewport.setShowJVSuffix(view.getShowFullId());
4429     af.viewport.setRightAlignIds(view.getRightAlignIds());
4430     af.viewport.setFont(
4431             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4432                     .getFontSize()), true);
4433     ViewStyleI vs = af.viewport.getViewStyle();
4434     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4435     af.viewport.setViewStyle(vs);
4436     // TODO: allow custom charWidth/Heights to be restored by updating them
4437     // after setting font - which means set above to false
4438     af.viewport.setRenderGaps(view.getRenderGaps());
4439     af.viewport.setWrapAlignment(view.getWrapAlignment());
4440     af.viewport.setShowAnnotation(view.getShowAnnotation());
4441
4442     af.viewport.setShowBoxes(view.getShowBoxes());
4443
4444     af.viewport.setShowText(view.getShowText());
4445
4446     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4447     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4448     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4449     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4450             .isShowUnconserved() : false);
4451     af.viewport.setStartRes(view.getStartRes());
4452     af.viewport.setStartSeq(view.getStartSeq());
4453     af.alignPanel.updateLayout();
4454     ColourSchemeI cs = null;
4455     // apply colourschemes
4456     if (view.getBgColour() != null)
4457     {
4458       if (view.getBgColour().startsWith("ucs"))
4459       {
4460         cs = getUserColourScheme(jms, view.getBgColour());
4461       }
4462       else if (view.getBgColour().startsWith("Annotation"))
4463       {
4464         AnnotationColours viewAnnColour = view.getAnnotationColours();
4465         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4466
4467         // annpos
4468
4469       }
4470       else
4471       {
4472         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4473       }
4474
4475       if (cs != null)
4476       {
4477         cs.setThreshold(view.getPidThreshold(), true);
4478         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4479       }
4480     }
4481
4482     af.viewport.setGlobalColourScheme(cs);
4483     af.viewport.setColourAppliesToAllGroups(false);
4484
4485     if (view.getConservationSelected() && cs != null)
4486     {
4487       cs.setConservationInc(view.getConsThreshold());
4488     }
4489
4490     af.changeColour(cs);
4491
4492     af.viewport.setColourAppliesToAllGroups(true);
4493
4494     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4495
4496     if (view.hasCentreColumnLabels())
4497     {
4498       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4499     }
4500     if (view.hasIgnoreGapsinConsensus())
4501     {
4502       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4503               null);
4504     }
4505     if (view.hasFollowHighlight())
4506     {
4507       af.viewport.setFollowHighlight(view.getFollowHighlight());
4508     }
4509     if (view.hasFollowSelection())
4510     {
4511       af.viewport.followSelection = view.getFollowSelection();
4512     }
4513     if (view.hasShowConsensusHistogram())
4514     {
4515       af.viewport.setShowConsensusHistogram(view
4516               .getShowConsensusHistogram());
4517     }
4518     else
4519     {
4520       af.viewport.setShowConsensusHistogram(true);
4521     }
4522     if (view.hasShowSequenceLogo())
4523     {
4524       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4525     }
4526     else
4527     {
4528       af.viewport.setShowSequenceLogo(false);
4529     }
4530     if (view.hasNormaliseSequenceLogo())
4531     {
4532       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4533     }
4534     if (view.hasShowDbRefTooltip())
4535     {
4536       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4537     }
4538     if (view.hasShowNPfeatureTooltip())
4539     {
4540       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4541     }
4542     if (view.hasShowGroupConsensus())
4543     {
4544       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4545     }
4546     else
4547     {
4548       af.viewport.setShowGroupConsensus(false);
4549     }
4550     if (view.hasShowGroupConservation())
4551     {
4552       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4553     }
4554     else
4555     {
4556       af.viewport.setShowGroupConservation(false);
4557     }
4558
4559     // recover featre settings
4560     if (jms.getFeatureSettings() != null)
4561     {
4562       FeaturesDisplayed fdi;
4563       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4564       String[] renderOrder = new String[jms.getFeatureSettings()
4565               .getSettingCount()];
4566       Map<String, FeatureColourI> featureColours = new Hashtable<String, FeatureColourI>();
4567       Map<String, Float> featureOrder = new Hashtable<String, Float>();
4568
4569       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4570       {
4571         Setting setting = jms.getFeatureSettings().getSetting(fs);
4572         if (setting.hasMincolour())
4573         {
4574           FeatureColourI gc = setting.hasMin() ? new FeatureColour(
4575                   new Color(setting.getMincolour()), new Color(
4576                           setting.getColour()), setting.getMin(),
4577                   setting.getMax()) : new FeatureColour(new Color(
4578                   setting.getMincolour()), new Color(setting.getColour()),
4579                   0, 1);
4580           if (setting.hasThreshold())
4581           {
4582             gc.setThreshold(setting.getThreshold());
4583             int threshstate = setting.getThreshstate();
4584             // -1 = None, 0 = Below, 1 = Above threshold
4585             if (threshstate == 0)
4586             {
4587               gc.setBelowThreshold(true);
4588             }
4589             else if (threshstate == 1)
4590             {
4591               gc.setAboveThreshold(true);
4592             }
4593           }
4594           gc.setAutoScaled(true); // default
4595           if (setting.hasAutoScale())
4596           {
4597             gc.setAutoScaled(setting.getAutoScale());
4598           }
4599           if (setting.hasColourByLabel())
4600           {
4601             gc.setColourByLabel(setting.getColourByLabel());
4602           }
4603           // and put in the feature colour table.
4604           featureColours.put(setting.getType(), gc);
4605         }
4606         else
4607         {
4608           featureColours.put(setting.getType(), new FeatureColour(
4609                   new Color(setting.getColour())));
4610         }
4611         renderOrder[fs] = setting.getType();
4612         if (setting.hasOrder())
4613         {
4614           featureOrder.put(setting.getType(), setting.getOrder());
4615         }
4616         else
4617         {
4618           featureOrder.put(setting.getType(), new Float(fs
4619                   / jms.getFeatureSettings().getSettingCount()));
4620         }
4621         if (setting.getDisplay())
4622         {
4623           fdi.setVisible(setting.getType());
4624         }
4625       }
4626       Map<String, Boolean> fgtable = new Hashtable<String, Boolean>();
4627       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4628       {
4629         Group grp = jms.getFeatureSettings().getGroup(gs);
4630         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4631       }
4632       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4633       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4634       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4635       FeatureRendererSettings frs = new FeatureRendererSettings(
4636               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4637       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4638               .transferSettings(frs);
4639
4640     }
4641
4642     if (view.getHiddenColumnsCount() > 0)
4643     {
4644       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4645       {
4646         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4647                 .getHiddenColumns(c).getEnd() // +1
4648                 );
4649       }
4650     }
4651     if (view.getCalcIdParam() != null)
4652     {
4653       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4654       {
4655         if (calcIdParam != null)
4656         {
4657           if (recoverCalcIdParam(calcIdParam, af.viewport))
4658           {
4659           }
4660           else
4661           {
4662             warn("Couldn't recover parameters for "
4663                     + calcIdParam.getCalcId());
4664           }
4665         }
4666       }
4667     }
4668     af.setMenusFromViewport(af.viewport);
4669     af.setTitle(view.getTitle());
4670     // TODO: we don't need to do this if the viewport is aready visible.
4671     /*
4672      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4673      * has a 'cdna/protein complement' view, in which case save it in order to
4674      * populate a SplitFrame once all views have been read in.
4675      */
4676     String complementaryViewId = view.getComplementId();
4677     if (complementaryViewId == null)
4678     {
4679       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4680               view.getHeight());
4681       // recompute any autoannotation
4682       af.alignPanel.updateAnnotation(false, true);
4683       reorderAutoannotation(af, al, autoAlan);
4684       af.alignPanel.alignmentChanged();
4685     }
4686     else
4687     {
4688       splitFrameCandidates.put(view, af);
4689     }
4690     return af;
4691   }
4692
4693   private ColourSchemeI constructAnnotationColour(
4694           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
4695           JalviewModelSequence jms, boolean checkGroupAnnColour)
4696   {
4697     boolean propagateAnnColour = false;
4698     ColourSchemeI cs = null;
4699     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4700     if (checkGroupAnnColour && al.getGroups() != null
4701             && al.getGroups().size() > 0)
4702     {
4703       // pre 2.8.1 behaviour
4704       // check to see if we should transfer annotation colours
4705       propagateAnnColour = true;
4706       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4707       {
4708         if (sg.cs instanceof AnnotationColourGradient)
4709         {
4710           propagateAnnColour = false;
4711         }
4712       }
4713     }
4714     // int find annotation
4715     if (annAlignment.getAlignmentAnnotation() != null)
4716     {
4717       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4718       {
4719         if (annAlignment.getAlignmentAnnotation()[i].label
4720                 .equals(viewAnnColour.getAnnotation()))
4721         {
4722           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4723           {
4724             annAlignment.getAlignmentAnnotation()[i]
4725                     .setThreshold(new jalview.datamodel.GraphLine(
4726                             viewAnnColour.getThreshold(), "Threshold",
4727                             java.awt.Color.black)
4728
4729                     );
4730           }
4731
4732           if (viewAnnColour.getColourScheme().equals("None"))
4733           {
4734             cs = new AnnotationColourGradient(
4735                     annAlignment.getAlignmentAnnotation()[i],
4736                     new java.awt.Color(viewAnnColour.getMinColour()),
4737                     new java.awt.Color(viewAnnColour.getMaxColour()),
4738                     viewAnnColour.getAboveThreshold());
4739           }
4740           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4741           {
4742             cs = new AnnotationColourGradient(
4743                     annAlignment.getAlignmentAnnotation()[i],
4744                     getUserColourScheme(jms,
4745                             viewAnnColour.getColourScheme()),
4746                     viewAnnColour.getAboveThreshold());
4747           }
4748           else
4749           {
4750             cs = new AnnotationColourGradient(
4751                     annAlignment.getAlignmentAnnotation()[i],
4752                     ColourSchemeProperty.getColour(al,
4753                             viewAnnColour.getColourScheme()),
4754                     viewAnnColour.getAboveThreshold());
4755           }
4756           if (viewAnnColour.hasPerSequence())
4757           {
4758             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4759                     .isPerSequence());
4760           }
4761           if (viewAnnColour.hasPredefinedColours())
4762           {
4763             ((AnnotationColourGradient) cs)
4764                     .setPredefinedColours(viewAnnColour
4765                             .isPredefinedColours());
4766           }
4767           if (propagateAnnColour && al.getGroups() != null)
4768           {
4769             // Also use these settings for all the groups
4770             for (int g = 0; g < al.getGroups().size(); g++)
4771             {
4772               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4773
4774               if (sg.cs == null)
4775               {
4776                 continue;
4777               }
4778
4779               /*
4780                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4781                * new AnnotationColourGradient(
4782                * annAlignment.getAlignmentAnnotation()[i], new
4783                * java.awt.Color(viewAnnColour. getMinColour()), new
4784                * java.awt.Color(viewAnnColour. getMaxColour()),
4785                * viewAnnColour.getAboveThreshold()); } else
4786                */
4787               {
4788                 sg.cs = new AnnotationColourGradient(
4789                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4790                         viewAnnColour.getAboveThreshold());
4791                 if (cs instanceof AnnotationColourGradient)
4792                 {
4793                   if (viewAnnColour.hasPerSequence())
4794                   {
4795                     ((AnnotationColourGradient) cs)
4796                             .setSeqAssociated(viewAnnColour.isPerSequence());
4797                   }
4798                   if (viewAnnColour.hasPredefinedColours())
4799                   {
4800                     ((AnnotationColourGradient) cs)
4801                             .setPredefinedColours(viewAnnColour
4802                                     .isPredefinedColours());
4803                   }
4804                 }
4805               }
4806
4807             }
4808           }
4809
4810           break;
4811         }
4812
4813       }
4814     }
4815     return cs;
4816   }
4817
4818   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
4819           List<JvAnnotRow> autoAlan)
4820   {
4821     // copy over visualization settings for autocalculated annotation in the
4822     // view
4823     if (al.getAlignmentAnnotation() != null)
4824     {
4825       /**
4826        * Kludge for magic autoannotation names (see JAL-811)
4827        */
4828       String[] magicNames = new String[] { "Consensus", "Quality",
4829           "Conservation" };
4830       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4831       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4832       for (String nm : magicNames)
4833       {
4834         visan.put(nm, nullAnnot);
4835       }
4836       for (JvAnnotRow auan : autoAlan)
4837       {
4838         visan.put(auan.template.label
4839                 + (auan.template.getCalcId() == null ? "" : "\t"
4840                         + auan.template.getCalcId()), auan);
4841       }
4842       int hSize = al.getAlignmentAnnotation().length;
4843       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4844       // work through any autoCalculated annotation already on the view
4845       // removing it if it should be placed in a different location on the
4846       // annotation panel.
4847       List<String> remains = new ArrayList<String>(visan.keySet());
4848       for (int h = 0; h < hSize; h++)
4849       {
4850         jalview.datamodel.AlignmentAnnotation jalan = al
4851                 .getAlignmentAnnotation()[h];
4852         if (jalan.autoCalculated)
4853         {
4854           String k;
4855           JvAnnotRow valan = visan.get(k = jalan.label);
4856           if (jalan.getCalcId() != null)
4857           {
4858             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4859           }
4860
4861           if (valan != null)
4862           {
4863             // delete the auto calculated row from the alignment
4864             al.deleteAnnotation(jalan, false);
4865             remains.remove(k);
4866             hSize--;
4867             h--;
4868             if (valan != nullAnnot)
4869             {
4870               if (jalan != valan.template)
4871               {
4872                 // newly created autoannotation row instance
4873                 // so keep a reference to the visible annotation row
4874                 // and copy over all relevant attributes
4875                 if (valan.template.graphHeight >= 0)
4876
4877                 {
4878                   jalan.graphHeight = valan.template.graphHeight;
4879                 }
4880                 jalan.visible = valan.template.visible;
4881               }
4882               reorder.add(new JvAnnotRow(valan.order, jalan));
4883             }
4884           }
4885         }
4886       }
4887       // Add any (possibly stale) autocalculated rows that were not appended to
4888       // the view during construction
4889       for (String other : remains)
4890       {
4891         JvAnnotRow othera = visan.get(other);
4892         if (othera != nullAnnot && othera.template.getCalcId() != null
4893                 && othera.template.getCalcId().length() > 0)
4894         {
4895           reorder.add(othera);
4896         }
4897       }
4898       // now put the automatic annotation in its correct place
4899       int s = 0, srt[] = new int[reorder.size()];
4900       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4901       for (JvAnnotRow jvar : reorder)
4902       {
4903         rws[s] = jvar;
4904         srt[s++] = jvar.order;
4905       }
4906       reorder.clear();
4907       jalview.util.QuickSort.sort(srt, rws);
4908       // and re-insert the annotation at its correct position
4909       for (JvAnnotRow jvar : rws)
4910       {
4911         al.addAnnotation(jvar.template, jvar.order);
4912       }
4913       af.alignPanel.adjustAnnotationHeight();
4914     }
4915   }
4916
4917   Hashtable skipList = null;
4918
4919   /**
4920    * TODO remove this method
4921    * 
4922    * @param view
4923    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4924    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4925    *         throw new Error("Implementation Error. No skipList defined for this
4926    *         Jalview2XML instance."); } return (AlignFrame)
4927    *         skipList.get(view.getSequenceSetId()); }
4928    */
4929
4930   /**
4931    * Check if the Jalview view contained in object should be skipped or not.
4932    * 
4933    * @param object
4934    * @return true if view's sequenceSetId is a key in skipList
4935    */
4936   private boolean skipViewport(JalviewModel object)
4937   {
4938     if (skipList == null)
4939     {
4940       return false;
4941     }
4942     String id;
4943     if (skipList.containsKey(id = object.getJalviewModelSequence()
4944             .getViewport()[0].getSequenceSetId()))
4945     {
4946       if (Cache.log != null && Cache.log.isDebugEnabled())
4947       {
4948         Cache.log.debug("Skipping seuqence set id " + id);
4949       }
4950       return true;
4951     }
4952     return false;
4953   }
4954
4955   public void addToSkipList(AlignFrame af)
4956   {
4957     if (skipList == null)
4958     {
4959       skipList = new Hashtable();
4960     }
4961     skipList.put(af.getViewport().getSequenceSetId(), af);
4962   }
4963
4964   public void clearSkipList()
4965   {
4966     if (skipList != null)
4967     {
4968       skipList.clear();
4969       skipList = null;
4970     }
4971   }
4972
4973   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
4974           boolean ignoreUnrefed)
4975   {
4976     jalview.datamodel.AlignmentI ds = getDatasetFor(vamsasSet
4977             .getDatasetId());
4978     Vector dseqs = null;
4979     if (ds == null)
4980     {
4981       // create a list of new dataset sequences
4982       dseqs = new Vector();
4983     }
4984     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4985     {
4986       Sequence vamsasSeq = vamsasSet.getSequence(i);
4987       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
4988     }
4989     // create a new dataset
4990     if (ds == null)
4991     {
4992       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4993       dseqs.copyInto(dsseqs);
4994       ds = new jalview.datamodel.Alignment(dsseqs);
4995       debug("Created new dataset " + vamsasSet.getDatasetId()
4996               + " for alignment " + System.identityHashCode(al));
4997       addDatasetRef(vamsasSet.getDatasetId(), ds);
4998     }
4999     // set the dataset for the newly imported alignment.
5000     if (al.getDataset() == null && !ignoreUnrefed)
5001     {
5002       al.setDataset(ds);
5003     }
5004   }
5005
5006   /**
5007    * 
5008    * @param vamsasSeq
5009    *          sequence definition to create/merge dataset sequence for
5010    * @param ds
5011    *          dataset alignment
5012    * @param dseqs
5013    *          vector to add new dataset sequence to
5014    * @param ignoreUnrefed
5015    *          - when true, don't create new sequences from vamsasSeq if it's id
5016    *          doesn't already have an asssociated Jalview sequence.
5017    * @param vseqpos
5018    *          - used to reorder the sequence in the alignment according to the
5019    *          vamsasSeq array ordering, to preserve ordering of dataset
5020    */
5021   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5022           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5023   {
5024     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5025     // xRef Codon Maps
5026     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5027     boolean reorder = false;
5028     SequenceI dsq = null;
5029     if (sq != null && sq.getDatasetSequence() != null)
5030     {
5031       dsq = sq.getDatasetSequence();
5032     }
5033     else
5034     {
5035       reorder = true;
5036     }
5037     if (sq == null && ignoreUnrefed)
5038     {
5039       return;
5040     }
5041     String sqid = vamsasSeq.getDsseqid();
5042     if (dsq == null)
5043     {
5044       // need to create or add a new dataset sequence reference to this sequence
5045       if (sqid != null)
5046       {
5047         dsq = seqRefIds.get(sqid);
5048       }
5049       // check again
5050       if (dsq == null)
5051       {
5052         // make a new dataset sequence
5053         dsq = sq.createDatasetSequence();
5054         if (sqid == null)
5055         {
5056           // make up a new dataset reference for this sequence
5057           sqid = seqHash(dsq);
5058         }
5059         dsq.setVamsasId(uniqueSetSuffix + sqid);
5060         seqRefIds.put(sqid, dsq);
5061         if (ds == null)
5062         {
5063           if (dseqs != null)
5064           {
5065             dseqs.addElement(dsq);
5066           }
5067         }
5068         else
5069         {
5070           ds.addSequence(dsq);
5071         }
5072       }
5073       else
5074       {
5075         if (sq != dsq)
5076         { // make this dataset sequence sq's dataset sequence
5077           sq.setDatasetSequence(dsq);
5078           // and update the current dataset alignment
5079           if (ds == null)
5080           {
5081             if (dseqs != null)
5082             {
5083               if (!dseqs.contains(dsq))
5084               {
5085                 dseqs.add(dsq);
5086               }
5087             }
5088             else
5089             {
5090               if (ds.findIndex(dsq) < 0)
5091               {
5092                 ds.addSequence(dsq);
5093               }
5094             }
5095           }
5096         }
5097       }
5098     }
5099     // TODO: refactor this as a merge dataset sequence function
5100     // now check that sq (the dataset sequence) sequence really is the union of
5101     // all references to it
5102     // boolean pre = sq.getStart() < dsq.getStart();
5103     // boolean post = sq.getEnd() > dsq.getEnd();
5104     // if (pre || post)
5105     if (sq != dsq)
5106     {
5107       // StringBuffer sb = new StringBuffer();
5108       String newres = jalview.analysis.AlignSeq.extractGaps(
5109               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5110       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5111               && newres.length() > dsq.getLength())
5112       {
5113         // Update with the longer sequence.
5114         synchronized (dsq)
5115         {
5116           /*
5117            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5118            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5119            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5120            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5121            */
5122           dsq.setSequence(newres);
5123         }
5124         // TODO: merges will never happen if we 'know' we have the real dataset
5125         // sequence - this should be detected when id==dssid
5126         System.err
5127                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5128         // + (pre ? "prepended" : "") + " "
5129         // + (post ? "appended" : ""));
5130       }
5131     }
5132     else
5133     {
5134       // sequence refs are identical. We may need to update the existing dataset
5135       // alignment with this one, though.
5136       if (ds != null && dseqs == null)
5137       {
5138         int opos = ds.findIndex(dsq);
5139         SequenceI tseq = null;
5140         if (opos != -1 && vseqpos != opos)
5141         {
5142           // remove from old position
5143           ds.deleteSequence(dsq);
5144         }
5145         if (vseqpos < ds.getHeight())
5146         {
5147           if (vseqpos != opos)
5148           {
5149             // save sequence at destination position
5150             tseq = ds.getSequenceAt(vseqpos);
5151             ds.replaceSequenceAt(vseqpos, dsq);
5152             ds.addSequence(tseq);
5153           }
5154         }
5155         else
5156         {
5157           ds.addSequence(dsq);
5158         }
5159       }
5160     }
5161   }
5162
5163   /*
5164    * TODO use AlignmentI here and in related methods - needs
5165    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5166    */
5167   Hashtable<String, AlignmentI> datasetIds = null;
5168
5169   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5170
5171   private AlignmentI getDatasetFor(String datasetId)
5172   {
5173     if (datasetIds == null)
5174     {
5175       datasetIds = new Hashtable<String, AlignmentI>();
5176       return null;
5177     }
5178     if (datasetIds.containsKey(datasetId))
5179     {
5180       return datasetIds.get(datasetId);
5181     }
5182     return null;
5183   }
5184
5185   private void addDatasetRef(String datasetId, AlignmentI dataset)
5186   {
5187     if (datasetIds == null)
5188     {
5189       datasetIds = new Hashtable<String, AlignmentI>();
5190     }
5191     datasetIds.put(datasetId, dataset);
5192   }
5193
5194   /**
5195    * make a new dataset ID for this jalview dataset alignment
5196    * 
5197    * @param dataset
5198    * @return
5199    */
5200   private String getDatasetIdRef(AlignmentI dataset)
5201   {
5202     if (dataset.getDataset() != null)
5203     {
5204       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5205     }
5206     String datasetId = makeHashCode(dataset, null);
5207     if (datasetId == null)
5208     {
5209       // make a new datasetId and record it
5210       if (dataset2Ids == null)
5211       {
5212         dataset2Ids = new IdentityHashMap<AlignmentI, String>();
5213       }
5214       else
5215       {
5216         datasetId = dataset2Ids.get(dataset);
5217       }
5218       if (datasetId == null)
5219       {
5220         datasetId = "ds" + dataset2Ids.size() + 1;
5221         dataset2Ids.put(dataset, datasetId);
5222       }
5223     }
5224     return datasetId;
5225   }
5226
5227   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5228   {
5229     for (int d = 0; d < sequence.getDBRefCount(); d++)
5230     {
5231       DBRef dr = sequence.getDBRef(d);
5232       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5233               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
5234                       .getVersion(), sequence.getDBRef(d).getAccessionId());
5235       if (dr.getMapping() != null)
5236       {
5237         entry.setMap(addMapping(dr.getMapping()));
5238       }
5239       datasetSequence.addDBRef(entry);
5240     }
5241   }
5242
5243   private jalview.datamodel.Mapping addMapping(Mapping m)
5244   {
5245     SequenceI dsto = null;
5246     // Mapping m = dr.getMapping();
5247     int fr[] = new int[m.getMapListFromCount() * 2];
5248     Enumeration f = m.enumerateMapListFrom();
5249     for (int _i = 0; f.hasMoreElements(); _i += 2)
5250     {
5251       MapListFrom mf = (MapListFrom) f.nextElement();
5252       fr[_i] = mf.getStart();
5253       fr[_i + 1] = mf.getEnd();
5254     }
5255     int fto[] = new int[m.getMapListToCount() * 2];
5256     f = m.enumerateMapListTo();
5257     for (int _i = 0; f.hasMoreElements(); _i += 2)
5258     {
5259       MapListTo mf = (MapListTo) f.nextElement();
5260       fto[_i] = mf.getStart();
5261       fto[_i + 1] = mf.getEnd();
5262     }
5263     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
5264             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
5265     if (m.getMappingChoice() != null)
5266     {
5267       MappingChoice mc = m.getMappingChoice();
5268       if (mc.getDseqFor() != null)
5269       {
5270         String dsfor = "" + mc.getDseqFor();
5271         if (seqRefIds.containsKey(dsfor))
5272         {
5273           /**
5274            * recover from hash
5275            */
5276           jmap.setTo(seqRefIds.get(dsfor));
5277         }
5278         else
5279         {
5280           frefedSequence.add(newMappingRef(dsfor, jmap));
5281         }
5282       }
5283       else
5284       {
5285         /**
5286          * local sequence definition
5287          */
5288         Sequence ms = mc.getSequence();
5289         SequenceI djs = null;
5290         String sqid = ms.getDsseqid();
5291         if (sqid != null && sqid.length() > 0)
5292         {
5293           /*
5294            * recover dataset sequence
5295            */
5296           djs = seqRefIds.get(sqid);
5297         }
5298         else
5299         {
5300           System.err
5301                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5302           sqid = ((Object) ms).toString(); // make up a new hascode for
5303           // undefined dataset sequence hash
5304           // (unlikely to happen)
5305         }
5306
5307         if (djs == null)
5308         {
5309           /**
5310            * make a new dataset sequence and add it to refIds hash
5311            */
5312           djs = new jalview.datamodel.Sequence(ms.getName(),
5313                   ms.getSequence());
5314           djs.setStart(jmap.getMap().getToLowest());
5315           djs.setEnd(jmap.getMap().getToHighest());
5316           djs.setVamsasId(uniqueSetSuffix + sqid);
5317           jmap.setTo(djs);
5318           incompleteSeqs.put(sqid, djs);
5319           seqRefIds.put(sqid, djs);
5320
5321         }
5322         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5323         addDBRefs(djs, ms);
5324
5325       }
5326     }
5327     return (jmap);
5328
5329   }
5330
5331   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5332           boolean keepSeqRefs)
5333   {
5334     initSeqRefs();
5335     JalviewModel jm = saveState(ap, null, null, null);
5336
5337     if (!keepSeqRefs)
5338     {
5339       clearSeqRefs();
5340       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5341     }
5342     else
5343     {
5344       uniqueSetSuffix = "";
5345       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5346       // overwrite the
5347       // view we just
5348       // copied
5349     }
5350     if (this.frefedSequence == null)
5351     {
5352       frefedSequence = new Vector();
5353     }
5354
5355     viewportsAdded.clear();
5356
5357     AlignFrame af = loadFromObject(jm, null, false, null);
5358     af.alignPanels.clear();
5359     af.closeMenuItem_actionPerformed(true);
5360
5361     /*
5362      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5363      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5364      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5365      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5366      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5367      */
5368
5369     return af.alignPanel;
5370   }
5371
5372   /**
5373    * flag indicating if hashtables should be cleared on finalization TODO this
5374    * flag may not be necessary
5375    */
5376   private final boolean _cleartables = true;
5377
5378   private Hashtable jvids2vobj;
5379
5380   /*
5381    * (non-Javadoc)
5382    * 
5383    * @see java.lang.Object#finalize()
5384    */
5385   @Override
5386   protected void finalize() throws Throwable
5387   {
5388     // really make sure we have no buried refs left.
5389     if (_cleartables)
5390     {
5391       clearSeqRefs();
5392     }
5393     this.seqRefIds = null;
5394     this.seqsToIds = null;
5395     super.finalize();
5396   }
5397
5398   private void warn(String msg)
5399   {
5400     warn(msg, null);
5401   }
5402
5403   private void warn(String msg, Exception e)
5404   {
5405     if (Cache.log != null)
5406     {
5407       if (e != null)
5408       {
5409         Cache.log.warn(msg, e);
5410       }
5411       else
5412       {
5413         Cache.log.warn(msg);
5414       }
5415     }
5416     else
5417     {
5418       System.err.println("Warning: " + msg);
5419       if (e != null)
5420       {
5421         e.printStackTrace();
5422       }
5423     }
5424   }
5425
5426   private void debug(String string)
5427   {
5428     debug(string, null);
5429   }
5430
5431   private void debug(String msg, Exception e)
5432   {
5433     if (Cache.log != null)
5434     {
5435       if (e != null)
5436       {
5437         Cache.log.debug(msg, e);
5438       }
5439       else
5440       {
5441         Cache.log.debug(msg);
5442       }
5443     }
5444     else
5445     {
5446       System.err.println("Warning: " + msg);
5447       if (e != null)
5448       {
5449         e.printStackTrace();
5450       }
5451     }
5452   }
5453
5454   /**
5455    * set the object to ID mapping tables used to write/recover objects and XML
5456    * ID strings for the jalview project. If external tables are provided then
5457    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5458    * object goes out of scope. - also populates the datasetIds hashtable with
5459    * alignment objects containing dataset sequences
5460    * 
5461    * @param vobj2jv
5462    *          Map from ID strings to jalview datamodel
5463    * @param jv2vobj
5464    *          Map from jalview datamodel to ID strings
5465    * 
5466    * 
5467    */
5468   public void setObjectMappingTables(Hashtable vobj2jv,
5469           IdentityHashMap jv2vobj)
5470   {
5471     this.jv2vobj = jv2vobj;
5472     this.vobj2jv = vobj2jv;
5473     Iterator ds = jv2vobj.keySet().iterator();
5474     String id;
5475     while (ds.hasNext())
5476     {
5477       Object jvobj = ds.next();
5478       id = jv2vobj.get(jvobj).toString();
5479       if (jvobj instanceof jalview.datamodel.Alignment)
5480       {
5481         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5482         {
5483           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5484         }
5485       }
5486       else if (jvobj instanceof jalview.datamodel.Sequence)
5487       {
5488         // register sequence object so the XML parser can recover it.
5489         if (seqRefIds == null)
5490         {
5491           seqRefIds = new HashMap<String, SequenceI>();
5492         }
5493         if (seqsToIds == null)
5494         {
5495           seqsToIds = new IdentityHashMap<SequenceI, String>();
5496         }
5497         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5498         seqsToIds.put((SequenceI) jvobj, id);
5499       }
5500       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5501       {
5502         String anid;
5503         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5504         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5505         if (jvann.annotationId == null)
5506         {
5507           jvann.annotationId = anid;
5508         }
5509         if (!jvann.annotationId.equals(anid))
5510         {
5511           // TODO verify that this is the correct behaviour
5512           this.warn("Overriding Annotation ID for " + anid
5513                   + " from different id : " + jvann.annotationId);
5514           jvann.annotationId = anid;
5515         }
5516       }
5517       else if (jvobj instanceof String)
5518       {
5519         if (jvids2vobj == null)
5520         {
5521           jvids2vobj = new Hashtable();
5522           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5523         }
5524       }
5525       else
5526       {
5527         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5528       }
5529     }
5530   }
5531
5532   /**
5533    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5534    * objects created from the project archive. If string is null (default for
5535    * construction) then suffix will be set automatically.
5536    * 
5537    * @param string
5538    */
5539   public void setUniqueSetSuffix(String string)
5540   {
5541     uniqueSetSuffix = string;
5542
5543   }
5544
5545   /**
5546    * uses skipList2 as the skipList for skipping views on sequence sets
5547    * associated with keys in the skipList
5548    * 
5549    * @param skipList2
5550    */
5551   public void setSkipList(Hashtable skipList2)
5552   {
5553     skipList = skipList2;
5554   }
5555
5556   /**
5557    * Reads the jar entry of given name and returns its contents, or null if the
5558    * entry is not found.
5559    * 
5560    * @param jprovider
5561    * @param jarEntryName
5562    * @return
5563    */
5564   protected String readJarEntry(jarInputStreamProvider jprovider,
5565           String jarEntryName)
5566   {
5567     String result = null;
5568     BufferedReader in = null;
5569
5570     try
5571     {
5572       /*
5573        * Reopen the jar input stream and traverse its entries to find a matching
5574        * name
5575        */
5576       JarInputStream jin = jprovider.getJarInputStream();
5577       JarEntry entry = null;
5578       do
5579       {
5580         entry = jin.getNextJarEntry();
5581       } while (entry != null && !entry.getName().equals(jarEntryName));
5582
5583       if (entry != null)
5584       {
5585         StringBuilder out = new StringBuilder(256);
5586         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5587         String data;
5588
5589         while ((data = in.readLine()) != null)
5590         {
5591           out.append(data);
5592         }
5593         result = out.toString();
5594       }
5595       else
5596       {
5597         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5598       }
5599     } catch (Exception ex)
5600     {
5601       ex.printStackTrace();
5602     } finally
5603     {
5604       if (in != null)
5605       {
5606         try
5607         {
5608           in.close();
5609         } catch (IOException e)
5610         {
5611           // ignore
5612         }
5613       }
5614     }
5615
5616     return result;
5617   }
5618
5619   /**
5620    * Returns an incrementing counter (0, 1, 2...)
5621    * 
5622    * @return
5623    */
5624   private synchronized int nextCounter()
5625   {
5626     return counter++;
5627   }
5628 }