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