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