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