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