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