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