JAL-3171 register existing dataset for new view
[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       {
3102         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
3103       }
3104
3105       if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
3106       {
3107         if (hiddenSeqs == null)
3108         {
3109           hiddenSeqs = new ArrayList<>();
3110         }
3111
3112         hiddenSeqs.add(tmpSeq);
3113       }
3114     }
3115
3116     // /
3117     // Create the alignment object from the sequence set
3118     // ///////////////////////////////
3119     SequenceI[] orderedSeqs = tmpseqs
3120             .toArray(new SequenceI[tmpseqs.size()]);
3121
3122     AlignmentI al = null;
3123     // so we must create or recover the dataset alignment before going further
3124     // ///////////////////////////////
3125     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
3126     {
3127       // older jalview projects do not have a dataset - so creat alignment and
3128       // dataset
3129       al = new Alignment(orderedSeqs);
3130       al.setDataset(null);
3131     }
3132     else
3133     {
3134       boolean isdsal = jalviewModel.getViewport().isEmpty();
3135       if (isdsal)
3136       {
3137         // we are importing a dataset record, so
3138         // recover reference to an alignment already materialsed as dataset
3139         al = getDatasetFor(vamsasSet.getDatasetId());
3140       }
3141       if (al == null)
3142       {
3143         // materialse the alignment
3144         al = new Alignment(orderedSeqs);
3145       }
3146       if (isdsal)
3147       {
3148         addDatasetRef(vamsasSet.getDatasetId(), al);
3149       }
3150
3151       // finally, verify all data in vamsasSet is actually present in al
3152       // passing on flag indicating if it is actually a stored dataset
3153       recoverDatasetFor(vamsasSet, al, isdsal);
3154     }
3155
3156     if (referenceseqForView != null)
3157     {
3158       al.setSeqrep(referenceseqForView);
3159     }
3160     // / Add the alignment properties
3161     for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
3162     {
3163       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
3164               .get(i);
3165       al.setProperty(ssp.getKey(), ssp.getValue());
3166     }
3167
3168     // ///////////////////////////////
3169
3170     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
3171     if (!multipleView)
3172     {
3173       // load sequence features, database references and any associated PDB
3174       // structures for the alignment
3175       //
3176       // prior to 2.10, this part would only be executed the first time a
3177       // sequence was encountered, but not afterwards.
3178       // now, for 2.10 projects, this is also done if the xml doc includes
3179       // dataset sequences not actually present in any particular view.
3180       //
3181       for (int i = 0; i < vamsasSeqs.size(); i++)
3182       {
3183         JSeq jseq = jseqs.get(i);
3184         if (jseq.getFeatures().size() > 0)
3185         {
3186           List<Feature> features = jseq.getFeatures();
3187           for (int f = 0; f < features.size(); f++)
3188           {
3189             Feature feat = features.get(f);
3190             SequenceFeature sf = new SequenceFeature(feat.getType(),
3191                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
3192                     safeFloat(feat.getScore()), feat.getFeatureGroup());
3193             sf.setStatus(feat.getStatus());
3194
3195             /*
3196              * load any feature attributes - include map-valued attributes
3197              */
3198             Map<String, Map<String, String>> mapAttributes = new HashMap<>();
3199             for (int od = 0; od < feat.getOtherData().size(); od++)
3200             {
3201               OtherData keyValue = feat.getOtherData().get(od);
3202               String attributeName = keyValue.getKey();
3203               String attributeValue = keyValue.getValue();
3204               if (attributeName.startsWith("LINK"))
3205               {
3206                 sf.addLink(attributeValue);
3207               }
3208               else
3209               {
3210                 String subAttribute = keyValue.getKey2();
3211                 if (subAttribute == null)
3212                 {
3213                   // simple string-valued attribute
3214                   sf.setValue(attributeName, attributeValue);
3215                 }
3216                 else
3217                 {
3218                   // attribute 'key' has sub-attribute 'key2'
3219                   if (!mapAttributes.containsKey(attributeName))
3220                   {
3221                     mapAttributes.put(attributeName, new HashMap<>());
3222                   }
3223                   mapAttributes.get(attributeName).put(subAttribute,
3224                           attributeValue);
3225                 }
3226               }
3227             }
3228             for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
3229                     .entrySet())
3230             {
3231               sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
3232             }
3233
3234             // adds feature to datasequence's feature set (since Jalview 2.10)
3235             al.getSequenceAt(i).addSequenceFeature(sf);
3236           }
3237         }
3238         if (vamsasSeqs.get(i).getDBRef().size() > 0)
3239         {
3240           // adds dbrefs to datasequence's set (since Jalview 2.10)
3241           addDBRefs(
3242                   al.getSequenceAt(i).getDatasetSequence() == null
3243                           ? al.getSequenceAt(i)
3244                           : al.getSequenceAt(i).getDatasetSequence(),
3245                   vamsasSeqs.get(i));
3246         }
3247         if (jseq.getPdbids().size() > 0)
3248         {
3249           List<Pdbids> ids = jseq.getPdbids();
3250           for (int p = 0; p < ids.size(); p++)
3251           {
3252             Pdbids pdbid = ids.get(p);
3253             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
3254             entry.setId(pdbid.getId());
3255             if (pdbid.getType() != null)
3256             {
3257               if (PDBEntry.Type.getType(pdbid.getType()) != null)
3258               {
3259                 entry.setType(PDBEntry.Type.getType(pdbid.getType()));
3260               }
3261               else
3262               {
3263                 entry.setType(PDBEntry.Type.FILE);
3264               }
3265             }
3266             // jprovider is null when executing 'New View'
3267             if (pdbid.getFile() != null && jprovider != null)
3268             {
3269               if (!pdbloaded.containsKey(pdbid.getFile()))
3270               {
3271                 entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
3272                         pdbid.getFile()));
3273               }
3274               else
3275               {
3276                 entry.setFile(pdbloaded.get(pdbid.getId()).toString());
3277               }
3278             }
3279             /*
3280             if (pdbid.getPdbentryItem() != null)
3281             {
3282               for (PdbentryItem item : pdbid.getPdbentryItem())
3283               {
3284                 for (Property pr : item.getProperty())
3285                 {
3286                   entry.setProperty(pr.getName(), pr.getValue());
3287                 }
3288               }
3289             }
3290             */
3291             for (Property prop : pdbid.getProperty())
3292             {
3293               entry.setProperty(prop.getName(), prop.getValue());
3294             }
3295             StructureSelectionManager
3296                     .getStructureSelectionManager(Desktop.instance)
3297                     .registerPDBEntry(entry);
3298             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3299             if (al.getSequenceAt(i).getDatasetSequence() != null)
3300             {
3301               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3302             }
3303             else
3304             {
3305               al.getSequenceAt(i).addPDBId(entry);
3306             }
3307           }
3308         }
3309       }
3310     } // end !multipleview
3311
3312     // ///////////////////////////////
3313     // LOAD SEQUENCE MAPPINGS
3314
3315     if (vamsasSet.getAlcodonFrame().size() > 0)
3316     {
3317       // TODO Potentially this should only be done once for all views of an
3318       // alignment
3319       List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
3320       for (int i = 0; i < alc.size(); i++)
3321       {
3322         AlignedCodonFrame cf = new AlignedCodonFrame();
3323         if (alc.get(i).getAlcodMap().size() > 0)
3324         {
3325           List<AlcodMap> maps = alc.get(i).getAlcodMap();
3326           for (int m = 0; m < maps.size(); m++)
3327           {
3328             AlcodMap map = maps.get(m);
3329             SequenceI dnaseq = seqRefIds.get(map.getDnasq());
3330             // Load Mapping
3331             jalview.datamodel.Mapping mapping = null;
3332             // attach to dna sequence reference.
3333             if (map.getMapping() != null)
3334             {
3335               mapping = addMapping(map.getMapping());
3336               if (dnaseq != null && mapping.getTo() != null)
3337               {
3338                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3339               }
3340               else
3341               {
3342                 // defer to later
3343                 frefedSequence.add(
3344                         newAlcodMapRef(map.getDnasq(), cf, mapping));
3345               }
3346             }
3347           }
3348           al.addCodonFrame(cf);
3349         }
3350       }
3351     }
3352
3353     // ////////////////////////////////
3354     // LOAD ANNOTATIONS
3355     List<JvAnnotRow> autoAlan = new ArrayList<>();
3356
3357     /*
3358      * store any annotations which forward reference a group's ID
3359      */
3360     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
3361
3362     if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
3363     {
3364       List<Annotation> an = vamsasSet.getAnnotation();
3365
3366       for (int i = 0; i < an.size(); i++)
3367       {
3368         Annotation annotation = an.get(i);
3369
3370         /**
3371          * test if annotation is automatically calculated for this view only
3372          */
3373         boolean autoForView = false;
3374         if (annotation.getLabel().equals("Quality")
3375                 || annotation.getLabel().equals("Conservation")
3376                 || annotation.getLabel().equals("Consensus"))
3377         {
3378           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3379           autoForView = true;
3380           // JAXB has no has() test; schema defaults value to false
3381           // if (!annotation.hasAutoCalculated())
3382           // {
3383           // annotation.setAutoCalculated(true);
3384           // }
3385         }
3386         if (autoForView || annotation.isAutoCalculated())
3387         {
3388           // remove ID - we don't recover annotation from other views for
3389           // view-specific annotation
3390           annotation.setId(null);
3391         }
3392
3393         // set visibility for other annotation in this view
3394         String annotationId = annotation.getId();
3395         if (annotationId != null && annotationIds.containsKey(annotationId))
3396         {
3397           AlignmentAnnotation jda = annotationIds.get(annotationId);
3398           // in principle Visible should always be true for annotation displayed
3399           // in multiple views
3400           if (annotation.isVisible() != null)
3401           {
3402             jda.visible = annotation.isVisible();
3403           }
3404
3405           al.addAnnotation(jda);
3406
3407           continue;
3408         }
3409         // Construct new annotation from model.
3410         List<AnnotationElement> ae = annotation.getAnnotationElement();
3411         jalview.datamodel.Annotation[] anot = null;
3412         java.awt.Color firstColour = null;
3413         int anpos;
3414         if (!annotation.isScoreOnly())
3415         {
3416           anot = new jalview.datamodel.Annotation[al.getWidth()];
3417           for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
3418           {
3419             AnnotationElement annElement = ae.get(aa);
3420             anpos = annElement.getPosition();
3421
3422             if (anpos >= anot.length)
3423             {
3424               continue;
3425             }
3426
3427             float value = safeFloat(annElement.getValue());
3428             anot[anpos] = new jalview.datamodel.Annotation(
3429                     annElement.getDisplayCharacter(),
3430                     annElement.getDescription(),
3431                     (annElement.getSecondaryStructure() == null
3432                             || annElement.getSecondaryStructure()
3433                                     .length() == 0)
3434                                             ? ' '
3435                                             : annElement
3436                                                     .getSecondaryStructure()
3437                                                     .charAt(0),
3438                     value);
3439             anot[anpos].colour = new Color(safeInt(annElement.getColour()));
3440             if (firstColour == null)
3441             {
3442               firstColour = anot[anpos].colour;
3443             }
3444           }
3445         }
3446         jalview.datamodel.AlignmentAnnotation jaa = null;
3447
3448         if (annotation.isGraph())
3449         {
3450           float llim = 0, hlim = 0;
3451           // if (autoForView || an[i].isAutoCalculated()) {
3452           // hlim=11f;
3453           // }
3454           jaa = new jalview.datamodel.AlignmentAnnotation(
3455                   annotation.getLabel(), annotation.getDescription(), anot,
3456                   llim, hlim, safeInt(annotation.getGraphType()));
3457
3458           jaa.graphGroup = safeInt(annotation.getGraphGroup());
3459           jaa._linecolour = firstColour;
3460           if (annotation.getThresholdLine() != null)
3461           {
3462             jaa.setThreshold(new jalview.datamodel.GraphLine(
3463                     safeFloat(annotation.getThresholdLine().getValue()),
3464                     annotation.getThresholdLine().getLabel(),
3465                     new java.awt.Color(safeInt(
3466                             annotation.getThresholdLine().getColour()))));
3467           }
3468           if (autoForView || annotation.isAutoCalculated())
3469           {
3470             // Hardwire the symbol display line to ensure that labels for
3471             // histograms are displayed
3472             jaa.hasText = true;
3473           }
3474         }
3475         else
3476         {
3477           jaa = new jalview.datamodel.AlignmentAnnotation(
3478                   annotation.getLabel(), annotation.getDescription(), anot);
3479           jaa._linecolour = firstColour;
3480         }
3481         // register new annotation
3482         if (annotation.getId() != null)
3483         {
3484           annotationIds.put(annotation.getId(), jaa);
3485           jaa.annotationId = annotation.getId();
3486         }
3487         // recover sequence association
3488         String sequenceRef = annotation.getSequenceRef();
3489         if (sequenceRef != null)
3490         {
3491           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3492           SequenceI sequence = seqRefIds.get(sequenceRef);
3493           if (sequence == null)
3494           {
3495             // in pre-2.9 projects sequence ref is to sequence name
3496             sequence = al.findName(sequenceRef);
3497           }
3498           if (sequence != null)
3499           {
3500             jaa.createSequenceMapping(sequence, 1, true);
3501             sequence.addAlignmentAnnotation(jaa);
3502           }
3503         }
3504         // and make a note of any group association
3505         if (annotation.getGroupRef() != null
3506                 && annotation.getGroupRef().length() > 0)
3507         {
3508           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3509                   .get(annotation.getGroupRef());
3510           if (aal == null)
3511           {
3512             aal = new ArrayList<>();
3513             groupAnnotRefs.put(annotation.getGroupRef(), aal);
3514           }
3515           aal.add(jaa);
3516         }
3517
3518         if (annotation.getScore() != null)
3519         {
3520           jaa.setScore(annotation.getScore().doubleValue());
3521         }
3522         if (annotation.isVisible() != null)
3523         {
3524           jaa.visible = annotation.isVisible().booleanValue();
3525         }
3526
3527         if (annotation.isCentreColLabels() != null)
3528         {
3529           jaa.centreColLabels = annotation.isCentreColLabels()
3530                   .booleanValue();
3531         }
3532
3533         if (annotation.isScaleColLabels() != null)
3534         {
3535           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
3536         }
3537         if (annotation.isAutoCalculated())
3538         {
3539           // newer files have an 'autoCalculated' flag and store calculation
3540           // state in viewport properties
3541           jaa.autoCalculated = true; // means annotation will be marked for
3542           // update at end of load.
3543         }
3544         if (annotation.getGraphHeight() != null)
3545         {
3546           jaa.graphHeight = annotation.getGraphHeight().intValue();
3547         }
3548         jaa.belowAlignment = annotation.isBelowAlignment();
3549         jaa.setCalcId(annotation.getCalcId());
3550         if (annotation.getProperty().size() > 0)
3551         {
3552           for (Annotation.Property prop : annotation
3553                   .getProperty())
3554           {
3555             jaa.setProperty(prop.getName(), prop.getValue());
3556           }
3557         }
3558         if (jaa.autoCalculated)
3559         {
3560           autoAlan.add(new JvAnnotRow(i, jaa));
3561         }
3562         else
3563         // if (!autoForView)
3564         {
3565           // add autocalculated group annotation and any user created annotation
3566           // for the view
3567           al.addAnnotation(jaa);
3568         }
3569       }
3570     }
3571     // ///////////////////////
3572     // LOAD GROUPS
3573     // Create alignment markup and styles for this view
3574     if (jalviewModel.getJGroup().size() > 0)
3575     {
3576       List<JGroup> groups = jalviewModel.getJGroup();
3577       boolean addAnnotSchemeGroup = false;
3578       for (int i = 0; i < groups.size(); i++)
3579       {
3580         JGroup jGroup = groups.get(i);
3581         ColourSchemeI cs = null;
3582         if (jGroup.getColour() != null)
3583         {
3584           if (jGroup.getColour().startsWith("ucs"))
3585           {
3586             cs = getUserColourScheme(jalviewModel, jGroup.getColour());
3587           }
3588           else if (jGroup.getColour().equals("AnnotationColourGradient")
3589                   && jGroup.getAnnotationColours() != null)
3590           {
3591             addAnnotSchemeGroup = true;
3592           }
3593           else
3594           {
3595             cs = ColourSchemeProperty.getColourScheme(al,
3596                     jGroup.getColour());
3597           }
3598         }
3599         int pidThreshold = safeInt(jGroup.getPidThreshold());
3600
3601         Vector<SequenceI> seqs = new Vector<>();
3602
3603         for (int s = 0; s < jGroup.getSeq().size(); s++)
3604         {
3605           String seqId = jGroup.getSeq().get(s);
3606           SequenceI ts = seqRefIds.get(seqId);
3607
3608           if (ts != null)
3609           {
3610             seqs.addElement(ts);
3611           }
3612         }
3613
3614         if (seqs.size() < 1)
3615         {
3616           continue;
3617         }
3618
3619         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3620                 safeBoolean(jGroup.isDisplayBoxes()),
3621                 safeBoolean(jGroup.isDisplayText()),
3622                 safeBoolean(jGroup.isColourText()),
3623                 safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
3624         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
3625         sg.getGroupColourScheme()
3626                 .setConservationInc(safeInt(jGroup.getConsThreshold()));
3627         sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
3628
3629         sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
3630         sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
3631         sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
3632         sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
3633         // attributes with a default in the schema are never null
3634           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3635           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3636           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3637         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
3638         if (jGroup.getConsThreshold() != null
3639                 && jGroup.getConsThreshold().intValue() != 0)
3640         {
3641           Conservation c = new Conservation("All", sg.getSequences(null), 0,
3642                   sg.getWidth() - 1);
3643           c.calculate();
3644           c.verdict(false, 25);
3645           sg.cs.setConservation(c);
3646         }
3647
3648         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3649         {
3650           // re-instate unique group/annotation row reference
3651           List<AlignmentAnnotation> jaal = groupAnnotRefs
3652                   .get(jGroup.getId());
3653           if (jaal != null)
3654           {
3655             for (AlignmentAnnotation jaa : jaal)
3656             {
3657               jaa.groupRef = sg;
3658               if (jaa.autoCalculated)
3659               {
3660                 // match up and try to set group autocalc alignment row for this
3661                 // annotation
3662                 if (jaa.label.startsWith("Consensus for "))
3663                 {
3664                   sg.setConsensus(jaa);
3665                 }
3666                 // match up and try to set group autocalc alignment row for this
3667                 // annotation
3668                 if (jaa.label.startsWith("Conservation for "))
3669                 {
3670                   sg.setConservationRow(jaa);
3671                 }
3672               }
3673             }
3674           }
3675         }
3676         al.addGroup(sg);
3677         if (addAnnotSchemeGroup)
3678         {
3679           // reconstruct the annotation colourscheme
3680           sg.setColourScheme(constructAnnotationColour(
3681                   jGroup.getAnnotationColours(), null, al, jalviewModel, false));
3682         }
3683       }
3684     }
3685     if (view == null)
3686     {
3687       // only dataset in this model, so just return.
3688       return null;
3689     }
3690     // ///////////////////////////////
3691     // LOAD VIEWPORT
3692
3693     // If we just load in the same jar file again, the sequenceSetId
3694     // will be the same, and we end up with multiple references
3695     // to the same sequenceSet. We must modify this id on load
3696     // so that each load of the file gives a unique id
3697     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3698     String viewId = (view.getId() == null ? null
3699             : view.getId() + uniqueSetSuffix);
3700     AlignFrame af = null;
3701     AlignViewport av = null;
3702     // now check to see if we really need to create a new viewport.
3703     if (multipleView && viewportsAdded.size() == 0)
3704     {
3705       // We recovered an alignment for which a viewport already exists.
3706       // TODO: fix up any settings necessary for overlaying stored state onto
3707       // state recovered from another document. (may not be necessary).
3708       // we may need a binding from a viewport in memory to one recovered from
3709       // XML.
3710       // and then recover its containing af to allow the settings to be applied.
3711       // TODO: fix for vamsas demo
3712       System.err.println(
3713               "About to recover a viewport for existing alignment: Sequence set ID is "
3714                       + uniqueSeqSetId);
3715       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3716       if (seqsetobj != null)
3717       {
3718         if (seqsetobj instanceof String)
3719         {
3720           uniqueSeqSetId = (String) seqsetobj;
3721           System.err.println(
3722                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3723                           + uniqueSeqSetId);
3724         }
3725         else
3726         {
3727           System.err.println(
3728                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
3729         }
3730
3731       }
3732     }
3733     /**
3734      * indicate that annotation colours are applied across all groups (pre
3735      * Jalview 2.8.1 behaviour)
3736      */
3737     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
3738             jalviewModel.getVersion());
3739
3740     AlignmentPanel ap = null;
3741     boolean isnewview = true;
3742     if (viewId != null)
3743     {
3744       // Check to see if this alignment already has a view id == viewId
3745       jalview.gui.AlignmentPanel views[] = Desktop
3746               .getAlignmentPanels(uniqueSeqSetId);
3747       if (views != null && views.length > 0)
3748       {
3749         for (int v = 0; v < views.length; v++)
3750         {
3751           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3752           {
3753             // recover the existing alignpanel, alignframe, viewport
3754             af = views[v].alignFrame;
3755             av = views[v].av;
3756             ap = views[v];
3757             // TODO: could even skip resetting view settings if we don't want to
3758             // change the local settings from other jalview processes
3759             isnewview = false;
3760           }
3761         }
3762       }
3763     }
3764
3765     if (isnewview)
3766     {
3767       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
3768               uniqueSeqSetId, viewId, autoAlan);
3769       av = af.getViewport();
3770       ap = af.alignPanel;
3771     }
3772
3773     /*
3774      * Load any trees, PDB structures and viewers
3775      * 
3776      * Not done if flag is false (when this method is used for New View)
3777      */
3778     if (loadTreesAndStructures)
3779     {
3780       loadTrees(jalviewModel, view, af, av, ap);
3781       loadPDBStructures(jprovider, jseqs, af, ap);
3782       loadRnaViewers(jprovider, jseqs, ap);
3783     }
3784     // and finally return.
3785     return af;
3786   }
3787
3788   /**
3789    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3790    * panel is restored from separate jar entries, two (gapped and trimmed) per
3791    * sequence and secondary structure.
3792    * 
3793    * Currently each viewer shows just one sequence and structure (gapped and
3794    * trimmed), however this method is designed to support multiple sequences or
3795    * structures in viewers if wanted in future.
3796    * 
3797    * @param jprovider
3798    * @param jseqs
3799    * @param ap
3800    */
3801   private void loadRnaViewers(jarInputStreamProvider jprovider,
3802           List<JSeq> jseqs, AlignmentPanel ap)
3803   {
3804     /*
3805      * scan the sequences for references to viewers; create each one the first
3806      * time it is referenced, add Rna models to existing viewers
3807      */
3808     for (JSeq jseq : jseqs)
3809     {
3810       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
3811       {
3812         RnaViewer viewer = jseq.getRnaViewer().get(i);
3813         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
3814                 ap);
3815
3816         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
3817         {
3818           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
3819           SequenceI seq = seqRefIds.get(jseq.getId());
3820           AlignmentAnnotation ann = this.annotationIds
3821                   .get(ss.getAnnotationId());
3822
3823           /*
3824            * add the structure to the Varna display (with session state copied
3825            * from the jar to a temporary file)
3826            */
3827           boolean gapped = safeBoolean(ss.isGapped());
3828           String rnaTitle = ss.getTitle();
3829           String sessionState = ss.getViewerState();
3830           String tempStateFile = copyJarEntry(jprovider, sessionState,
3831                   "varna", null);
3832           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3833           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3834         }
3835         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
3836       }
3837     }
3838   }
3839
3840   /**
3841    * Locate and return an already instantiated matching AppVarna, or create one
3842    * if not found
3843    * 
3844    * @param viewer
3845    * @param viewIdSuffix
3846    * @param ap
3847    * @return
3848    */
3849   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3850           String viewIdSuffix, AlignmentPanel ap)
3851   {
3852     /*
3853      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3854      * if load is repeated
3855      */
3856     String postLoadId = viewer.getViewId() + viewIdSuffix;
3857     for (JInternalFrame frame : getAllFrames())
3858     {
3859       if (frame instanceof AppVarna)
3860       {
3861         AppVarna varna = (AppVarna) frame;
3862         if (postLoadId.equals(varna.getViewId()))
3863         {
3864           // this viewer is already instantiated
3865           // could in future here add ap as another 'parent' of the
3866           // AppVarna window; currently just 1-to-many
3867           return varna;
3868         }
3869       }
3870     }
3871
3872     /*
3873      * viewer not found - make it
3874      */
3875     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
3876             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
3877             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
3878             safeInt(viewer.getDividerLocation()));
3879     AppVarna varna = new AppVarna(model, ap);
3880
3881     return varna;
3882   }
3883
3884   /**
3885    * Load any saved trees
3886    * 
3887    * @param jm
3888    * @param view
3889    * @param af
3890    * @param av
3891    * @param ap
3892    */
3893   protected void loadTrees(JalviewModel jm, Viewport view,
3894           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3895   {
3896     // TODO result of automated refactoring - are all these parameters needed?
3897     try
3898     {
3899       for (int t = 0; t < jm.getTree().size(); t++)
3900       {
3901
3902         Tree tree = jm.getTree().get(t);
3903
3904         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3905         if (tp == null)
3906         {
3907           tp = af.showNewickTree(new NewickFile(tree.getNewick()),
3908                   tree.getTitle(), safeInt(tree.getWidth()),
3909                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
3910                   safeInt(tree.getYpos()));
3911           if (tree.getId() != null)
3912           {
3913             // perhaps bind the tree id to something ?
3914           }
3915         }
3916         else
3917         {
3918           // update local tree attributes ?
3919           // TODO: should check if tp has been manipulated by user - if so its
3920           // settings shouldn't be modified
3921           tp.setTitle(tree.getTitle());
3922           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
3923                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
3924                   safeInt(tree.getHeight())));
3925           tp.setViewport(av); // af.viewport;
3926           // TODO: verify 'associate with all views' works still
3927           tp.getTreeCanvas().setViewport(av); // af.viewport;
3928           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
3929
3930         }
3931         if (tp == null)
3932         {
3933           warn("There was a problem recovering stored Newick tree: \n"
3934                   + tree.getNewick());
3935           continue;
3936         }
3937
3938         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
3939         tp.fitToWindow_actionPerformed(null);
3940
3941         if (tree.getFontName() != null)
3942         {
3943           tp.setTreeFont(
3944                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
3945                           safeInt(tree.getFontSize())));
3946         }
3947         else
3948         {
3949           tp.setTreeFont(
3950                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
3951                           safeInt(view.getFontSize())));
3952         }
3953
3954         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
3955         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
3956         tp.showDistances(safeBoolean(tree.isShowDistances()));
3957
3958         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
3959
3960         if (safeBoolean(tree.isCurrentTree()))
3961         {
3962           af.getViewport().setCurrentTree(tp.getTree());
3963         }
3964       }
3965
3966     } catch (Exception ex)
3967     {
3968       ex.printStackTrace();
3969     }
3970   }
3971
3972   /**
3973    * Load and link any saved structure viewers.
3974    * 
3975    * @param jprovider
3976    * @param jseqs
3977    * @param af
3978    * @param ap
3979    */
3980   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3981           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
3982   {
3983     /*
3984      * Run through all PDB ids on the alignment, and collect mappings between
3985      * distinct view ids and all sequences referring to that view.
3986      */
3987     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
3988
3989     for (int i = 0; i < jseqs.size(); i++)
3990     {
3991       JSeq jseq = jseqs.get(i);
3992       if (jseq.getPdbids().size() > 0)
3993       {
3994         List<Pdbids> ids = jseq.getPdbids();
3995         for (int p = 0; p < ids.size(); p++)
3996         {
3997           Pdbids pdbid = ids.get(p);
3998           final int structureStateCount = pdbid.getStructureState().size();
3999           for (int s = 0; s < structureStateCount; s++)
4000           {
4001             // check to see if we haven't already created this structure view
4002             final StructureState structureState = pdbid
4003                     .getStructureState().get(s);
4004             String sviewid = (structureState.getViewId() == null) ? null
4005                     : structureState.getViewId() + uniqueSetSuffix;
4006             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4007             // Originally : pdbid.getFile()
4008             // : TODO: verify external PDB file recovery still works in normal
4009             // jalview project load
4010             jpdb.setFile(
4011                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4012             jpdb.setId(pdbid.getId());
4013
4014             int x = safeInt(structureState.getXpos());
4015             int y = safeInt(structureState.getYpos());
4016             int width = safeInt(structureState.getWidth());
4017             int height = safeInt(structureState.getHeight());
4018
4019             // Probably don't need to do this anymore...
4020             // Desktop.desktop.getComponentAt(x, y);
4021             // TODO: NOW: check that this recovers the PDB file correctly.
4022             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4023                     pdbid.getFile());
4024             jalview.datamodel.SequenceI seq = seqRefIds
4025                     .get(jseq.getId() + "");
4026             if (sviewid == null)
4027             {
4028               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4029                       + height;
4030             }
4031             if (!structureViewers.containsKey(sviewid))
4032             {
4033               structureViewers.put(sviewid,
4034                       new StructureViewerModel(x, y, width, height, false,
4035                               false, true, structureState.getViewId(),
4036                               structureState.getType()));
4037               // Legacy pre-2.7 conversion JAL-823 :
4038               // do not assume any view has to be linked for colour by
4039               // sequence
4040             }
4041
4042             // assemble String[] { pdb files }, String[] { id for each
4043             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4044             // seqs_file 2}, boolean[] {
4045             // linkAlignPanel,superposeWithAlignpanel}} from hash
4046             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4047             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4048                     || structureState.isAlignwithAlignPanel());
4049
4050             /*
4051              * Default colour by linked panel to false if not specified (e.g.
4052              * for pre-2.7 projects)
4053              */
4054             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4055             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4056             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4057
4058             /*
4059              * Default colour by viewer to true if not specified (e.g. for
4060              * pre-2.7 projects)
4061              */
4062             boolean colourByViewer = jmoldat.isColourByViewer();
4063             colourByViewer &= structureState.isColourByJmol();
4064             jmoldat.setColourByViewer(colourByViewer);
4065
4066             if (jmoldat.getStateData().length() < structureState
4067                     .getValue()/*Content()*/.length())
4068             {
4069               jmoldat.setStateData(structureState.getValue());// Content());
4070             }
4071             if (pdbid.getFile() != null)
4072             {
4073               File mapkey = new File(pdbid.getFile());
4074               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4075               if (seqstrmaps == null)
4076               {
4077                 jmoldat.getFileData().put(mapkey,
4078                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4079                                 pdbid.getId()));
4080               }
4081               if (!seqstrmaps.getSeqList().contains(seq))
4082               {
4083                 seqstrmaps.getSeqList().add(seq);
4084                 // TODO and chains?
4085               }
4086             }
4087             else
4088             {
4089               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");
4090               warn(errorMessage);
4091             }
4092           }
4093         }
4094       }
4095     }
4096     // Instantiate the associated structure views
4097     for (Entry<String, StructureViewerModel> entry : structureViewers
4098             .entrySet())
4099     {
4100       try
4101       {
4102         createOrLinkStructureViewer(entry, af, ap, jprovider);
4103       } catch (Exception e)
4104       {
4105         System.err.println(
4106                 "Error loading structure viewer: " + e.getMessage());
4107         // failed - try the next one
4108       }
4109     }
4110   }
4111
4112   /**
4113    * 
4114    * @param viewerData
4115    * @param af
4116    * @param ap
4117    * @param jprovider
4118    */
4119   protected void createOrLinkStructureViewer(
4120           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4121           AlignmentPanel ap, jarInputStreamProvider jprovider)
4122   {
4123     final StructureViewerModel stateData = viewerData.getValue();
4124
4125     /*
4126      * Search for any viewer windows already open from other alignment views
4127      * that exactly match the stored structure state
4128      */
4129     StructureViewerBase comp = findMatchingViewer(viewerData);
4130
4131     if (comp != null)
4132     {
4133       linkStructureViewer(ap, comp, stateData);
4134       return;
4135     }
4136
4137     /*
4138      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
4139      * "viewer_"+stateData.viewId
4140      */
4141     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
4142     {
4143       createChimeraViewer(viewerData, af, jprovider);
4144     }
4145     else
4146     {
4147       /*
4148        * else Jmol (if pre-2.9, stateData contains JMOL state string)
4149        */
4150       createJmolViewer(viewerData, af, jprovider);
4151     }
4152   }
4153
4154   /**
4155    * Create a new Chimera viewer.
4156    * 
4157    * @param data
4158    * @param af
4159    * @param jprovider
4160    */
4161   protected void createChimeraViewer(
4162           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4163           jarInputStreamProvider jprovider)
4164   {
4165     StructureViewerModel data = viewerData.getValue();
4166     String chimeraSessionFile = data.getStateData();
4167
4168     /*
4169      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
4170      * 
4171      * NB this is the 'saved' viewId as in the project file XML, _not_ the
4172      * 'uniquified' sviewid used to reconstruct the viewer here
4173      */
4174     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
4175     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
4176             "chimera", null);
4177
4178     Set<Entry<File, StructureData>> fileData = data.getFileData()
4179             .entrySet();
4180     List<PDBEntry> pdbs = new ArrayList<>();
4181     List<SequenceI[]> allseqs = new ArrayList<>();
4182     for (Entry<File, StructureData> pdb : fileData)
4183     {
4184       String filePath = pdb.getValue().getFilePath();
4185       String pdbId = pdb.getValue().getPdbId();
4186       // pdbs.add(new PDBEntry(filePath, pdbId));
4187       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4188       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4189       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4190       allseqs.add(seqs);
4191     }
4192
4193     boolean colourByChimera = data.isColourByViewer();
4194     boolean colourBySequence = data.isColourWithAlignPanel();
4195
4196     // TODO use StructureViewer as a factory here, see JAL-1761
4197     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4198     final SequenceI[][] seqsArray = allseqs
4199             .toArray(new SequenceI[allseqs.size()][]);
4200     String newViewId = viewerData.getKey();
4201
4202     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4203             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4204             colourBySequence, newViewId);
4205     cvf.setSize(data.getWidth(), data.getHeight());
4206     cvf.setLocation(data.getX(), data.getY());
4207   }
4208
4209   /**
4210    * Create a new Jmol window. First parse the Jmol state to translate filenames
4211    * loaded into the view, and record the order in which files are shown in the
4212    * Jmol view, so we can add the sequence mappings in same order.
4213    * 
4214    * @param viewerData
4215    * @param af
4216    * @param jprovider
4217    */
4218   protected void createJmolViewer(
4219           final Entry<String, StructureViewerModel> viewerData,
4220           AlignFrame af, jarInputStreamProvider jprovider)
4221   {
4222     final StructureViewerModel svattrib = viewerData.getValue();
4223     String state = svattrib.getStateData();
4224
4225     /*
4226      * Pre-2.9: state element value is the Jmol state string
4227      * 
4228      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4229      * + viewId
4230      */
4231     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4232     {
4233       state = readJarEntry(jprovider,
4234               getViewerJarEntryName(svattrib.getViewId()));
4235     }
4236
4237     List<String> pdbfilenames = new ArrayList<>();
4238     List<SequenceI[]> seqmaps = new ArrayList<>();
4239     List<String> pdbids = new ArrayList<>();
4240     StringBuilder newFileLoc = new StringBuilder(64);
4241     int cp = 0, ncp, ecp;
4242     Map<File, StructureData> oldFiles = svattrib.getFileData();
4243     while ((ncp = state.indexOf("load ", cp)) > -1)
4244     {
4245       do
4246       {
4247         // look for next filename in load statement
4248         newFileLoc.append(state.substring(cp,
4249                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4250         String oldfilenam = state.substring(ncp,
4251                 ecp = state.indexOf("\"", ncp));
4252         // recover the new mapping data for this old filename
4253         // have to normalize filename - since Jmol and jalview do
4254         // filename
4255         // translation differently.
4256         StructureData filedat = oldFiles.get(new File(oldfilenam));
4257         if (filedat == null)
4258         {
4259           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4260           filedat = oldFiles.get(new File(reformatedOldFilename));
4261         }
4262         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4263         pdbfilenames.add(filedat.getFilePath());
4264         pdbids.add(filedat.getPdbId());
4265         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4266         newFileLoc.append("\"");
4267         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4268                       // look for next file statement.
4269       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4270     }
4271     if (cp > 0)
4272     {
4273       // just append rest of state
4274       newFileLoc.append(state.substring(cp));
4275     }
4276     else
4277     {
4278       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4279       newFileLoc = new StringBuilder(state);
4280       newFileLoc.append("; load append ");
4281       for (File id : oldFiles.keySet())
4282       {
4283         // add this and any other pdb files that should be present in
4284         // the viewer
4285         StructureData filedat = oldFiles.get(id);
4286         newFileLoc.append(filedat.getFilePath());
4287         pdbfilenames.add(filedat.getFilePath());
4288         pdbids.add(filedat.getPdbId());
4289         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4290         newFileLoc.append(" \"");
4291         newFileLoc.append(filedat.getFilePath());
4292         newFileLoc.append("\"");
4293
4294       }
4295       newFileLoc.append(";");
4296     }
4297
4298     if (newFileLoc.length() == 0)
4299     {
4300       return;
4301     }
4302     int histbug = newFileLoc.indexOf("history = ");
4303     if (histbug > -1)
4304     {
4305       /*
4306        * change "history = [true|false];" to "history = [1|0];"
4307        */
4308       histbug += 10;
4309       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4310       String val = (diff == -1) ? null
4311               : newFileLoc.substring(histbug, diff);
4312       if (val != null && val.length() >= 4)
4313       {
4314         if (val.contains("e")) // eh? what can it be?
4315         {
4316           if (val.trim().equals("true"))
4317           {
4318             val = "1";
4319           }
4320           else
4321           {
4322             val = "0";
4323           }
4324           newFileLoc.replace(histbug, diff, val);
4325         }
4326       }
4327     }
4328
4329     final String[] pdbf = pdbfilenames
4330             .toArray(new String[pdbfilenames.size()]);
4331     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4332     final SequenceI[][] sq = seqmaps
4333             .toArray(new SequenceI[seqmaps.size()][]);
4334     final String fileloc = newFileLoc.toString();
4335     final String sviewid = viewerData.getKey();
4336     final AlignFrame alf = af;
4337     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4338             svattrib.getWidth(), svattrib.getHeight());
4339     try
4340     {
4341       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4342       {
4343         @Override
4344         public void run()
4345         {
4346           JalviewStructureDisplayI sview = null;
4347           try
4348           {
4349             sview = new StructureViewer(
4350                     alf.alignPanel.getStructureSelectionManager())
4351                             .createView(StructureViewer.ViewerType.JMOL,
4352                                     pdbf, id, sq, alf.alignPanel, svattrib,
4353                                     fileloc, rect, sviewid);
4354             addNewStructureViewer(sview);
4355           } catch (OutOfMemoryError ex)
4356           {
4357             new OOMWarning("restoring structure view for PDB id " + id,
4358                     (OutOfMemoryError) ex.getCause());
4359             if (sview != null && sview.isVisible())
4360             {
4361               sview.closeViewer(false);
4362               sview.setVisible(false);
4363               sview.dispose();
4364             }
4365           }
4366         }
4367       });
4368     } catch (InvocationTargetException ex)
4369     {
4370       warn("Unexpected error when opening Jmol view.", ex);
4371
4372     } catch (InterruptedException e)
4373     {
4374       // e.printStackTrace();
4375     }
4376
4377   }
4378
4379   /**
4380    * Generates a name for the entry in the project jar file to hold state
4381    * information for a structure viewer
4382    * 
4383    * @param viewId
4384    * @return
4385    */
4386   protected String getViewerJarEntryName(String viewId)
4387   {
4388     return VIEWER_PREFIX + viewId;
4389   }
4390
4391   /**
4392    * Returns any open frame that matches given structure viewer data. The match
4393    * is based on the unique viewId, or (for older project versions) the frame's
4394    * geometry.
4395    * 
4396    * @param viewerData
4397    * @return
4398    */
4399   protected StructureViewerBase findMatchingViewer(
4400           Entry<String, StructureViewerModel> viewerData)
4401   {
4402     final String sviewid = viewerData.getKey();
4403     final StructureViewerModel svattrib = viewerData.getValue();
4404     StructureViewerBase comp = null;
4405     JInternalFrame[] frames = getAllFrames();
4406     for (JInternalFrame frame : frames)
4407     {
4408       if (frame instanceof StructureViewerBase)
4409       {
4410         /*
4411          * Post jalview 2.4 schema includes structure view id
4412          */
4413         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4414                 .equals(sviewid))
4415         {
4416           comp = (StructureViewerBase) frame;
4417           break; // break added in 2.9
4418         }
4419         /*
4420          * Otherwise test for matching position and size of viewer frame
4421          */
4422         else if (frame.getX() == svattrib.getX()
4423                 && frame.getY() == svattrib.getY()
4424                 && frame.getHeight() == svattrib.getHeight()
4425                 && frame.getWidth() == svattrib.getWidth())
4426         {
4427           comp = (StructureViewerBase) frame;
4428           // no break in faint hope of an exact match on viewId
4429         }
4430       }
4431     }
4432     return comp;
4433   }
4434
4435   /**
4436    * Link an AlignmentPanel to an existing structure viewer.
4437    * 
4438    * @param ap
4439    * @param viewer
4440    * @param oldFiles
4441    * @param useinViewerSuperpos
4442    * @param usetoColourbyseq
4443    * @param viewerColouring
4444    */
4445   protected void linkStructureViewer(AlignmentPanel ap,
4446           StructureViewerBase viewer, StructureViewerModel stateData)
4447   {
4448     // NOTE: if the jalview project is part of a shared session then
4449     // view synchronization should/could be done here.
4450
4451     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4452     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4453     final boolean viewerColouring = stateData.isColourByViewer();
4454     Map<File, StructureData> oldFiles = stateData.getFileData();
4455
4456     /*
4457      * Add mapping for sequences in this view to an already open viewer
4458      */
4459     final AAStructureBindingModel binding = viewer.getBinding();
4460     for (File id : oldFiles.keySet())
4461     {
4462       // add this and any other pdb files that should be present in the
4463       // viewer
4464       StructureData filedat = oldFiles.get(id);
4465       String pdbFile = filedat.getFilePath();
4466       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4467       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4468               null);
4469       binding.addSequenceForStructFile(pdbFile, seq);
4470     }
4471     // and add the AlignmentPanel's reference to the view panel
4472     viewer.addAlignmentPanel(ap);
4473     if (useinViewerSuperpos)
4474     {
4475       viewer.useAlignmentPanelForSuperposition(ap);
4476     }
4477     else
4478     {
4479       viewer.excludeAlignmentPanelForSuperposition(ap);
4480     }
4481     if (usetoColourbyseq)
4482     {
4483       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4484     }
4485     else
4486     {
4487       viewer.excludeAlignmentPanelForColourbyseq(ap);
4488     }
4489   }
4490
4491   /**
4492    * Get all frames within the Desktop.
4493    * 
4494    * @return
4495    */
4496   protected JInternalFrame[] getAllFrames()
4497   {
4498     JInternalFrame[] frames = null;
4499     // TODO is this necessary - is it safe - risk of hanging?
4500     do
4501     {
4502       try
4503       {
4504         frames = Desktop.desktop.getAllFrames();
4505       } catch (ArrayIndexOutOfBoundsException e)
4506       {
4507         // occasional No such child exceptions are thrown here...
4508         try
4509         {
4510           Thread.sleep(10);
4511         } catch (InterruptedException f)
4512         {
4513         }
4514       }
4515     } while (frames == null);
4516     return frames;
4517   }
4518
4519   /**
4520    * Answers true if 'version' is equal to or later than 'supported', where each
4521    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4522    * changes. Development and test values for 'version' are leniently treated
4523    * i.e. answer true.
4524    * 
4525    * @param supported
4526    *          - minimum version we are comparing against
4527    * @param version
4528    *          - version of data being processsed
4529    * @return
4530    */
4531   public static boolean isVersionStringLaterThan(String supported,
4532           String version)
4533   {
4534     if (supported == null || version == null
4535             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4536             || version.equalsIgnoreCase("Test")
4537             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4538     {
4539       System.err.println("Assuming project file with "
4540               + (version == null ? "null" : version)
4541               + " is compatible with Jalview version " + supported);
4542       return true;
4543     }
4544     else
4545     {
4546       return StringUtils.compareVersions(version, supported, "b") >= 0;
4547     }
4548   }
4549
4550   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4551
4552   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4553   {
4554     if (newStructureViewers != null)
4555     {
4556       sview.getBinding().setFinishedLoadingFromArchive(false);
4557       newStructureViewers.add(sview);
4558     }
4559   }
4560
4561   protected void setLoadingFinishedForNewStructureViewers()
4562   {
4563     if (newStructureViewers != null)
4564     {
4565       for (JalviewStructureDisplayI sview : newStructureViewers)
4566       {
4567         sview.getBinding().setFinishedLoadingFromArchive(true);
4568       }
4569       newStructureViewers.clear();
4570       newStructureViewers = null;
4571     }
4572   }
4573
4574   AlignFrame loadViewport(String file, List<JSeq> JSEQ,
4575           List<SequenceI> hiddenSeqs, AlignmentI al,
4576           JalviewModel jm, Viewport view, String uniqueSeqSetId,
4577           String viewId, List<JvAnnotRow> autoAlan)
4578   {
4579     AlignFrame af = null;
4580     af = new AlignFrame(al, safeInt(view.getWidth()),
4581             safeInt(view.getHeight()), uniqueSeqSetId, viewId);
4582
4583     af.setFileName(file, FileFormat.Jalview);
4584
4585     final AlignViewport viewport = af.getViewport();
4586     for (int i = 0; i < JSEQ.size(); i++)
4587     {
4588       int colour = safeInt(JSEQ.get(i).getColour());
4589       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
4590               new Color(colour));
4591     }
4592
4593     if (al.hasSeqrep())
4594     {
4595       viewport.setColourByReferenceSeq(true);
4596       viewport.setDisplayReferenceSeq(true);
4597     }
4598
4599     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
4600
4601     if (view.getSequenceSetId() != null)
4602     {
4603       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4604
4605       viewport.setSequenceSetId(uniqueSeqSetId);
4606       if (av != null)
4607       {
4608         // propagate shared settings to this new view
4609         viewport.setHistoryList(av.getHistoryList());
4610         viewport.setRedoList(av.getRedoList());
4611       }
4612       else
4613       {
4614         viewportsAdded.put(uniqueSeqSetId, viewport);
4615       }
4616       // TODO: check if this method can be called repeatedly without
4617       // side-effects if alignpanel already registered.
4618       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4619     }
4620     // apply Hidden regions to view.
4621     if (hiddenSeqs != null)
4622     {
4623       for (int s = 0; s < JSEQ.size(); s++)
4624       {
4625         SequenceGroup hidden = new SequenceGroup();
4626         boolean isRepresentative = false;
4627         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
4628         {
4629           isRepresentative = true;
4630           SequenceI sequenceToHide = al
4631                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
4632           hidden.addSequence(sequenceToHide, false);
4633           // remove from hiddenSeqs list so we don't try to hide it twice
4634           hiddenSeqs.remove(sequenceToHide);
4635         }
4636         if (isRepresentative)
4637         {
4638           SequenceI representativeSequence = al.getSequenceAt(s);
4639           hidden.addSequence(representativeSequence, false);
4640           viewport.hideRepSequences(representativeSequence, hidden);
4641         }
4642       }
4643
4644       SequenceI[] hseqs = hiddenSeqs
4645               .toArray(new SequenceI[hiddenSeqs.size()]);
4646       viewport.hideSequence(hseqs);
4647
4648     }
4649     // recover view properties and display parameters
4650
4651     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4652     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
4653     final int pidThreshold = safeInt(view.getPidThreshold());
4654     viewport.setThreshold(pidThreshold);
4655
4656     viewport.setColourText(safeBoolean(view.isShowColourText()));
4657
4658     viewport
4659             .setConservationSelected(
4660                     safeBoolean(view.isConservationSelected()));
4661     viewport.setIncrement(safeInt(view.getConsThreshold()));
4662     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
4663     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
4664     viewport.setFont(new Font(view.getFontName(),
4665             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
4666             true);
4667     ViewStyleI vs = viewport.getViewStyle();
4668     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4669     viewport.setViewStyle(vs);
4670     // TODO: allow custom charWidth/Heights to be restored by updating them
4671     // after setting font - which means set above to false
4672     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
4673     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
4674     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
4675
4676     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
4677
4678     viewport.setShowText(safeBoolean(view.isShowText()));
4679
4680     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
4681     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
4682     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
4683     viewport.setShowUnconserved(view.isShowUnconserved());
4684     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
4685
4686     if (view.getViewName() != null)
4687     {
4688       viewport.setViewName(view.getViewName());
4689       af.setInitialTabVisible();
4690     }
4691     af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
4692             safeInt(view.getWidth()), safeInt(view.getHeight()));
4693     // startSeq set in af.alignPanel.updateLayout below
4694     af.alignPanel.updateLayout();
4695     ColourSchemeI cs = null;
4696     // apply colourschemes
4697     if (view.getBgColour() != null)
4698     {
4699       if (view.getBgColour().startsWith("ucs"))
4700       {
4701         cs = getUserColourScheme(jm, view.getBgColour());
4702       }
4703       else if (view.getBgColour().startsWith("Annotation"))
4704       {
4705         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
4706         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
4707
4708         // annpos
4709
4710       }
4711       else
4712       {
4713         cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour());
4714       }
4715     }
4716
4717     viewport.setGlobalColourScheme(cs);
4718     viewport.getResidueShading().setThreshold(pidThreshold,
4719             view.isIgnoreGapsinConsensus());
4720     viewport.getResidueShading()
4721             .setConsensus(viewport.getSequenceConsensusHash());
4722     viewport.setColourAppliesToAllGroups(false);
4723
4724     if (safeBoolean(view.isConservationSelected()) && cs != null)
4725     {
4726       viewport.getResidueShading()
4727               .setConservationInc(safeInt(view.getConsThreshold()));
4728     }
4729
4730     af.changeColour(cs);
4731
4732     viewport.setColourAppliesToAllGroups(true);
4733
4734     viewport
4735             .setShowSequenceFeatures(
4736                     safeBoolean(view.isShowSequenceFeatures()));
4737
4738     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
4739     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
4740     viewport.setFollowHighlight(view.isFollowHighlight());
4741     viewport.followSelection = view.isFollowSelection();
4742     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
4743     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
4744     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
4745     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
4746     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
4747     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
4748     viewport.setShowGroupConservation(view.isShowGroupConservation());
4749
4750     // recover feature settings
4751     if (jm.getFeatureSettings() != null)
4752     {
4753       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
4754               .getFeatureRenderer();
4755       FeaturesDisplayed fdi;
4756       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4757       String[] renderOrder = new String[jm.getFeatureSettings()
4758               .getSetting().size()];
4759       Map<String, FeatureColourI> featureColours = new Hashtable<>();
4760       Map<String, Float> featureOrder = new Hashtable<>();
4761
4762       for (int fs = 0; fs < jm.getFeatureSettings()
4763               .getSetting().size(); fs++)
4764       {
4765         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
4766         String featureType = setting.getType();
4767
4768         /*
4769          * restore feature filters (if any)
4770          */
4771         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
4772                 .getMatcherSet();
4773         if (filters != null)
4774         {
4775           FeatureMatcherSetI filter = Jalview2XML
4776                   .parseFilter(featureType, filters);
4777           if (!filter.isEmpty())
4778           {
4779             fr.setFeatureFilter(featureType, filter);
4780           }
4781         }
4782
4783         /*
4784          * restore feature colour scheme
4785          */
4786         Color maxColour = new Color(setting.getColour());
4787         if (setting.getMincolour() != null)
4788         {
4789           /*
4790            * minColour is always set unless a simple colour
4791            * (including for colour by label though it doesn't use it)
4792            */
4793           Color minColour = new Color(setting.getMincolour().intValue());
4794           Color noValueColour = minColour;
4795           NoValueColour noColour = setting.getNoValueColour();
4796           if (noColour == NoValueColour.NONE)
4797           {
4798             noValueColour = null;
4799           }
4800           else if (noColour == NoValueColour.MAX)
4801           {
4802             noValueColour = maxColour;
4803           }
4804           float min = safeFloat(safeFloat(setting.getMin()));
4805           float max = setting.getMax() == null ? 1f
4806                   : setting.getMax().floatValue();
4807           FeatureColourI gc = new FeatureColour(minColour, maxColour,
4808                   noValueColour, min, max);
4809           if (setting.getAttributeName().size() > 0)
4810           {
4811             gc.setAttributeName(setting.getAttributeName().toArray(
4812                     new String[setting.getAttributeName().size()]));
4813           }
4814           if (setting.getThreshold() != null)
4815           {
4816             gc.setThreshold(setting.getThreshold().floatValue());
4817             int threshstate = safeInt(setting.getThreshstate());
4818             // -1 = None, 0 = Below, 1 = Above threshold
4819             if (threshstate == 0)
4820             {
4821               gc.setBelowThreshold(true);
4822             }
4823             else if (threshstate == 1)
4824             {
4825               gc.setAboveThreshold(true);
4826             }
4827           }
4828           gc.setAutoScaled(true); // default
4829           if (setting.isAutoScale() != null)
4830           {
4831             gc.setAutoScaled(setting.isAutoScale());
4832           }
4833           if (setting.isColourByLabel() != null)
4834           {
4835             gc.setColourByLabel(setting.isColourByLabel());
4836           }
4837           // and put in the feature colour table.
4838           featureColours.put(featureType, gc);
4839         }
4840         else
4841         {
4842           featureColours.put(featureType,
4843                   new FeatureColour(maxColour));
4844         }
4845         renderOrder[fs] = featureType;
4846         if (setting.getOrder() != null)
4847         {
4848           featureOrder.put(featureType, setting.getOrder().floatValue());
4849         }
4850         else
4851         {
4852           featureOrder.put(featureType, new Float(
4853                   fs / jm.getFeatureSettings().getSetting().size()));
4854         }
4855         if (safeBoolean(setting.isDisplay()))
4856         {
4857           fdi.setVisible(featureType);
4858         }
4859       }
4860       Map<String, Boolean> fgtable = new Hashtable<>();
4861       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
4862       {
4863         Group grp = jm.getFeatureSettings().getGroup().get(gs);
4864         fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
4865       }
4866       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4867       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4868       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4869       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4870               fgtable, featureColours, 1.0f, featureOrder);
4871       fr.transferSettings(frs);
4872     }
4873
4874     if (view.getHiddenColumns().size() > 0)
4875     {
4876       for (int c = 0; c < view.getHiddenColumns().size(); c++)
4877       {
4878         final HiddenColumns hc = view.getHiddenColumns().get(c);
4879         viewport.hideColumns(safeInt(hc.getStart()),
4880                 safeInt(hc.getEnd()) /* +1 */);
4881       }
4882     }
4883     if (view.getCalcIdParam() != null)
4884     {
4885       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4886       {
4887         if (calcIdParam != null)
4888         {
4889           if (recoverCalcIdParam(calcIdParam, viewport))
4890           {
4891           }
4892           else
4893           {
4894             warn("Couldn't recover parameters for "
4895                     + calcIdParam.getCalcId());
4896           }
4897         }
4898       }
4899     }
4900     af.setMenusFromViewport(viewport);
4901     af.setTitle(view.getTitle());
4902     // TODO: we don't need to do this if the viewport is aready visible.
4903     /*
4904      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4905      * has a 'cdna/protein complement' view, in which case save it in order to
4906      * populate a SplitFrame once all views have been read in.
4907      */
4908     String complementaryViewId = view.getComplementId();
4909     if (complementaryViewId == null)
4910     {
4911       Desktop.addInternalFrame(af, view.getTitle(),
4912               safeInt(view.getWidth()), safeInt(view.getHeight()));
4913       // recompute any autoannotation
4914       af.alignPanel.updateAnnotation(false, true);
4915       reorderAutoannotation(af, al, autoAlan);
4916       af.alignPanel.alignmentChanged();
4917     }
4918     else
4919     {
4920       splitFrameCandidates.put(view, af);
4921     }
4922     return af;
4923   }
4924
4925   /**
4926    * Reads saved data to restore Colour by Annotation settings
4927    * 
4928    * @param viewAnnColour
4929    * @param af
4930    * @param al
4931    * @param model
4932    * @param checkGroupAnnColour
4933    * @return
4934    */
4935   private ColourSchemeI constructAnnotationColour(
4936           AnnotationColourScheme viewAnnColour, AlignFrame af,
4937           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
4938   {
4939     boolean propagateAnnColour = false;
4940     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
4941             : al;
4942     if (checkGroupAnnColour && al.getGroups() != null
4943             && al.getGroups().size() > 0)
4944     {
4945       // pre 2.8.1 behaviour
4946       // check to see if we should transfer annotation colours
4947       propagateAnnColour = true;
4948       for (SequenceGroup sg : al.getGroups())
4949       {
4950         if (sg.getColourScheme() instanceof AnnotationColourGradient)
4951         {
4952           propagateAnnColour = false;
4953         }
4954       }
4955     }
4956
4957     /*
4958      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
4959      */
4960     String annotationId = viewAnnColour.getAnnotation();
4961     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
4962
4963     /*
4964      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
4965      */
4966     if (matchedAnnotation == null
4967             && annAlignment.getAlignmentAnnotation() != null)
4968     {
4969       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4970       {
4971         if (annotationId
4972                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
4973         {
4974           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
4975           break;
4976         }
4977       }
4978     }
4979     if (matchedAnnotation == null)
4980     {
4981       System.err.println("Failed to match annotation colour scheme for "
4982               + annotationId);
4983       return null;
4984     }
4985     if (matchedAnnotation.getThreshold() == null)
4986     {
4987       matchedAnnotation.setThreshold(
4988               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
4989                       "Threshold", Color.black));
4990     }
4991
4992     AnnotationColourGradient cs = null;
4993     if (viewAnnColour.getColourScheme().equals("None"))
4994     {
4995       cs = new AnnotationColourGradient(matchedAnnotation,
4996               new Color(safeInt(viewAnnColour.getMinColour())),
4997               new Color(safeInt(viewAnnColour.getMaxColour())),
4998               safeInt(viewAnnColour.getAboveThreshold()));
4999     }
5000     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5001     {
5002       cs = new AnnotationColourGradient(matchedAnnotation,
5003               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5004               safeInt(viewAnnColour.getAboveThreshold()));
5005     }
5006     else
5007     {
5008       cs = new AnnotationColourGradient(matchedAnnotation,
5009               ColourSchemeProperty.getColourScheme(al,
5010                       viewAnnColour.getColourScheme()),
5011               safeInt(viewAnnColour.getAboveThreshold()));
5012     }
5013
5014     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5015     boolean useOriginalColours = safeBoolean(
5016             viewAnnColour.isPredefinedColours());
5017     cs.setSeqAssociated(perSequenceOnly);
5018     cs.setPredefinedColours(useOriginalColours);
5019
5020     if (propagateAnnColour && al.getGroups() != null)
5021     {
5022       // Also use these settings for all the groups
5023       for (int g = 0; g < al.getGroups().size(); g++)
5024       {
5025         SequenceGroup sg = al.getGroups().get(g);
5026         if (sg.getGroupColourScheme() == null)
5027         {
5028           continue;
5029         }
5030
5031         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5032                 matchedAnnotation, sg.getColourScheme(),
5033                 safeInt(viewAnnColour.getAboveThreshold()));
5034         sg.setColourScheme(groupScheme);
5035         groupScheme.setSeqAssociated(perSequenceOnly);
5036         groupScheme.setPredefinedColours(useOriginalColours);
5037       }
5038     }
5039     return cs;
5040   }
5041
5042   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5043           List<JvAnnotRow> autoAlan)
5044   {
5045     // copy over visualization settings for autocalculated annotation in the
5046     // view
5047     if (al.getAlignmentAnnotation() != null)
5048     {
5049       /**
5050        * Kludge for magic autoannotation names (see JAL-811)
5051        */
5052       String[] magicNames = new String[] { "Consensus", "Quality",
5053           "Conservation" };
5054       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5055       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5056       for (String nm : magicNames)
5057       {
5058         visan.put(nm, nullAnnot);
5059       }
5060       for (JvAnnotRow auan : autoAlan)
5061       {
5062         visan.put(auan.template.label
5063                 + (auan.template.getCalcId() == null ? ""
5064                         : "\t" + auan.template.getCalcId()),
5065                 auan);
5066       }
5067       int hSize = al.getAlignmentAnnotation().length;
5068       List<JvAnnotRow> reorder = new ArrayList<>();
5069       // work through any autoCalculated annotation already on the view
5070       // removing it if it should be placed in a different location on the
5071       // annotation panel.
5072       List<String> remains = new ArrayList<>(visan.keySet());
5073       for (int h = 0; h < hSize; h++)
5074       {
5075         jalview.datamodel.AlignmentAnnotation jalan = al
5076                 .getAlignmentAnnotation()[h];
5077         if (jalan.autoCalculated)
5078         {
5079           String k;
5080           JvAnnotRow valan = visan.get(k = jalan.label);
5081           if (jalan.getCalcId() != null)
5082           {
5083             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5084           }
5085
5086           if (valan != null)
5087           {
5088             // delete the auto calculated row from the alignment
5089             al.deleteAnnotation(jalan, false);
5090             remains.remove(k);
5091             hSize--;
5092             h--;
5093             if (valan != nullAnnot)
5094             {
5095               if (jalan != valan.template)
5096               {
5097                 // newly created autoannotation row instance
5098                 // so keep a reference to the visible annotation row
5099                 // and copy over all relevant attributes
5100                 if (valan.template.graphHeight >= 0)
5101
5102                 {
5103                   jalan.graphHeight = valan.template.graphHeight;
5104                 }
5105                 jalan.visible = valan.template.visible;
5106               }
5107               reorder.add(new JvAnnotRow(valan.order, jalan));
5108             }
5109           }
5110         }
5111       }
5112       // Add any (possibly stale) autocalculated rows that were not appended to
5113       // the view during construction
5114       for (String other : remains)
5115       {
5116         JvAnnotRow othera = visan.get(other);
5117         if (othera != nullAnnot && othera.template.getCalcId() != null
5118                 && othera.template.getCalcId().length() > 0)
5119         {
5120           reorder.add(othera);
5121         }
5122       }
5123       // now put the automatic annotation in its correct place
5124       int s = 0, srt[] = new int[reorder.size()];
5125       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5126       for (JvAnnotRow jvar : reorder)
5127       {
5128         rws[s] = jvar;
5129         srt[s++] = jvar.order;
5130       }
5131       reorder.clear();
5132       jalview.util.QuickSort.sort(srt, rws);
5133       // and re-insert the annotation at its correct position
5134       for (JvAnnotRow jvar : rws)
5135       {
5136         al.addAnnotation(jvar.template, jvar.order);
5137       }
5138       af.alignPanel.adjustAnnotationHeight();
5139     }
5140   }
5141
5142   Hashtable skipList = null;
5143
5144   /**
5145    * TODO remove this method
5146    * 
5147    * @param view
5148    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5149    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5150    *         throw new Error("Implementation Error. No skipList defined for this
5151    *         Jalview2XML instance."); } return (AlignFrame)
5152    *         skipList.get(view.getSequenceSetId()); }
5153    */
5154
5155   /**
5156    * Check if the Jalview view contained in object should be skipped or not.
5157    * 
5158    * @param object
5159    * @return true if view's sequenceSetId is a key in skipList
5160    */
5161   private boolean skipViewport(JalviewModel object)
5162   {
5163     if (skipList == null)
5164     {
5165       return false;
5166     }
5167     String id = object.getViewport().get(0).getSequenceSetId();
5168     if (skipList.containsKey(id))
5169     {
5170       if (Cache.log != null && Cache.log.isDebugEnabled())
5171       {
5172         Cache.log.debug("Skipping seuqence set id " + id);
5173       }
5174       return true;
5175     }
5176     return false;
5177   }
5178
5179   public void addToSkipList(AlignFrame af)
5180   {
5181     if (skipList == null)
5182     {
5183       skipList = new Hashtable();
5184     }
5185     skipList.put(af.getViewport().getSequenceSetId(), af);
5186   }
5187
5188   public void clearSkipList()
5189   {
5190     if (skipList != null)
5191     {
5192       skipList.clear();
5193       skipList = null;
5194     }
5195   }
5196
5197   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5198           boolean ignoreUnrefed)
5199   {
5200     jalview.datamodel.AlignmentI ds = getDatasetFor(
5201             vamsasSet.getDatasetId());
5202     Vector dseqs = null;
5203     if (ds == null)
5204     {
5205       // create a list of new dataset sequences
5206       dseqs = new Vector();
5207     }
5208     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5209     {
5210       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5211       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5212     }
5213     // create a new dataset
5214     if (ds == null)
5215     {
5216       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5217       dseqs.copyInto(dsseqs);
5218       ds = new jalview.datamodel.Alignment(dsseqs);
5219       debug("Created new dataset " + vamsasSet.getDatasetId()
5220               + " for alignment " + System.identityHashCode(al));
5221       addDatasetRef(vamsasSet.getDatasetId(), ds);
5222     }
5223     // set the dataset for the newly imported alignment.
5224     if (al.getDataset() == null && !ignoreUnrefed)
5225     {
5226       al.setDataset(ds);
5227     }
5228   }
5229
5230   /**
5231    * 
5232    * @param vamsasSeq
5233    *          sequence definition to create/merge dataset sequence for
5234    * @param ds
5235    *          dataset alignment
5236    * @param dseqs
5237    *          vector to add new dataset sequence to
5238    * @param ignoreUnrefed
5239    *          - when true, don't create new sequences from vamsasSeq if it's id
5240    *          doesn't already have an asssociated Jalview sequence.
5241    * @param vseqpos
5242    *          - used to reorder the sequence in the alignment according to the
5243    *          vamsasSeq array ordering, to preserve ordering of dataset
5244    */
5245   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5246           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5247   {
5248     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5249     // xRef Codon Maps
5250     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5251     boolean reorder = false;
5252     SequenceI dsq = null;
5253     if (sq != null && sq.getDatasetSequence() != null)
5254     {
5255       dsq = sq.getDatasetSequence();
5256     }
5257     else
5258     {
5259       reorder = true;
5260     }
5261     if (sq == null && ignoreUnrefed)
5262     {
5263       return;
5264     }
5265     String sqid = vamsasSeq.getDsseqid();
5266     if (dsq == null)
5267     {
5268       // need to create or add a new dataset sequence reference to this sequence
5269       if (sqid != null)
5270       {
5271         dsq = seqRefIds.get(sqid);
5272       }
5273       // check again
5274       if (dsq == null)
5275       {
5276         // make a new dataset sequence
5277         dsq = sq.createDatasetSequence();
5278         if (sqid == null)
5279         {
5280           // make up a new dataset reference for this sequence
5281           sqid = seqHash(dsq);
5282         }
5283         dsq.setVamsasId(uniqueSetSuffix + sqid);
5284         seqRefIds.put(sqid, dsq);
5285         if (ds == null)
5286         {
5287           if (dseqs != null)
5288           {
5289             dseqs.addElement(dsq);
5290           }
5291         }
5292         else
5293         {
5294           ds.addSequence(dsq);
5295         }
5296       }
5297       else
5298       {
5299         if (sq != dsq)
5300         { // make this dataset sequence sq's dataset sequence
5301           sq.setDatasetSequence(dsq);
5302           // and update the current dataset alignment
5303           if (ds == null)
5304           {
5305             if (dseqs != null)
5306             {
5307               if (!dseqs.contains(dsq))
5308               {
5309                 dseqs.add(dsq);
5310               }
5311             }
5312             else
5313             {
5314               if (ds.findIndex(dsq) < 0)
5315               {
5316                 ds.addSequence(dsq);
5317               }
5318             }
5319           }
5320         }
5321       }
5322     }
5323     // TODO: refactor this as a merge dataset sequence function
5324     // now check that sq (the dataset sequence) sequence really is the union of
5325     // all references to it
5326     // boolean pre = sq.getStart() < dsq.getStart();
5327     // boolean post = sq.getEnd() > dsq.getEnd();
5328     // if (pre || post)
5329     if (sq != dsq)
5330     {
5331       // StringBuffer sb = new StringBuffer();
5332       String newres = jalview.analysis.AlignSeq.extractGaps(
5333               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5334       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5335               && newres.length() > dsq.getLength())
5336       {
5337         // Update with the longer sequence.
5338         synchronized (dsq)
5339         {
5340           /*
5341            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5342            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5343            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5344            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5345            */
5346           dsq.setSequence(newres);
5347         }
5348         // TODO: merges will never happen if we 'know' we have the real dataset
5349         // sequence - this should be detected when id==dssid
5350         System.err.println(
5351                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5352         // + (pre ? "prepended" : "") + " "
5353         // + (post ? "appended" : ""));
5354       }
5355     }
5356     else
5357     {
5358       // sequence refs are identical. We may need to update the existing dataset
5359       // alignment with this one, though.
5360       if (ds != null && dseqs == null)
5361       {
5362         int opos = ds.findIndex(dsq);
5363         SequenceI tseq = null;
5364         if (opos != -1 && vseqpos != opos)
5365         {
5366           // remove from old position
5367           ds.deleteSequence(dsq);
5368         }
5369         if (vseqpos < ds.getHeight())
5370         {
5371           if (vseqpos != opos)
5372           {
5373             // save sequence at destination position
5374             tseq = ds.getSequenceAt(vseqpos);
5375             ds.replaceSequenceAt(vseqpos, dsq);
5376             ds.addSequence(tseq);
5377           }
5378         }
5379         else
5380         {
5381           ds.addSequence(dsq);
5382         }
5383       }
5384     }
5385   }
5386
5387   /*
5388    * TODO use AlignmentI here and in related methods - needs
5389    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5390    */
5391   Hashtable<String, AlignmentI> datasetIds = null;
5392
5393   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5394
5395   private AlignmentI getDatasetFor(String datasetId)
5396   {
5397     if (datasetIds == null)
5398     {
5399       datasetIds = new Hashtable<>();
5400       return null;
5401     }
5402     if (datasetIds.containsKey(datasetId))
5403     {
5404       return datasetIds.get(datasetId);
5405     }
5406     return null;
5407   }
5408
5409   private void addDatasetRef(String datasetId, AlignmentI dataset)
5410   {
5411     if (datasetIds == null)
5412     {
5413       datasetIds = new Hashtable<>();
5414     }
5415     datasetIds.put(datasetId, dataset);
5416   }
5417
5418   /**
5419    * make a new dataset ID for this jalview dataset alignment
5420    * 
5421    * @param dataset
5422    * @return
5423    */
5424   private String getDatasetIdRef(AlignmentI dataset)
5425   {
5426     if (dataset.getDataset() != null)
5427     {
5428       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5429     }
5430     String datasetId = makeHashCode(dataset, null);
5431     if (datasetId == null)
5432     {
5433       // make a new datasetId and record it
5434       if (dataset2Ids == null)
5435       {
5436         dataset2Ids = new IdentityHashMap<>();
5437       }
5438       else
5439       {
5440         datasetId = dataset2Ids.get(dataset);
5441       }
5442       if (datasetId == null)
5443       {
5444         datasetId = "ds" + dataset2Ids.size() + 1;
5445         dataset2Ids.put(dataset, datasetId);
5446       }
5447     }
5448     return datasetId;
5449   }
5450
5451   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5452   {
5453     for (int d = 0; d < sequence.getDBRef().size(); d++)
5454     {
5455       DBRef dr = sequence.getDBRef().get(d);
5456       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5457               dr.getSource(), dr.getVersion(), dr.getAccessionId());
5458       if (dr.getMapping() != null)
5459       {
5460         entry.setMap(addMapping(dr.getMapping()));
5461       }
5462       datasetSequence.addDBRef(entry);
5463     }
5464   }
5465
5466   private jalview.datamodel.Mapping addMapping(Mapping m)
5467   {
5468     SequenceI dsto = null;
5469     // Mapping m = dr.getMapping();
5470     int fr[] = new int[m.getMapListFrom().size() * 2];
5471     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
5472     for (int _i = 0; from.hasNext(); _i += 2)
5473     {
5474       MapListFrom mf = from.next();
5475       fr[_i] = mf.getStart();
5476       fr[_i + 1] = mf.getEnd();
5477     }
5478     int fto[] = new int[m.getMapListTo().size() * 2];
5479     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
5480     for (int _i = 0; to.hasNext(); _i += 2)
5481     {
5482       MapListTo mf = to.next();
5483       fto[_i] = mf.getStart();
5484       fto[_i + 1] = mf.getEnd();
5485     }
5486     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5487             fto, m.getMapFromUnit().intValue(),
5488             m.getMapToUnit().intValue());
5489     // if (m.getMappingChoice() != null)
5490     // {
5491     // MappingChoice mc = m.getMappingChoice();
5492     if (m.getDseqFor() != null)
5493     {
5494       String dsfor = m.getDseqFor();
5495       if (seqRefIds.containsKey(dsfor))
5496       {
5497         /**
5498          * recover from hash
5499          */
5500         jmap.setTo(seqRefIds.get(dsfor));
5501       }
5502       else
5503       {
5504         frefedSequence.add(newMappingRef(dsfor, jmap));
5505       }
5506     }
5507     else
5508     {
5509       /**
5510        * local sequence definition
5511        */
5512       Sequence ms = m.getSequence();
5513       SequenceI djs = null;
5514       String sqid = ms.getDsseqid();
5515       if (sqid != null && sqid.length() > 0)
5516       {
5517         /*
5518          * recover dataset sequence
5519          */
5520         djs = seqRefIds.get(sqid);
5521       }
5522       else
5523       {
5524         System.err.println(
5525                 "Warning - making up dataset sequence id for DbRef sequence map reference");
5526         sqid = ((Object) ms).toString(); // make up a new hascode for
5527         // undefined dataset sequence hash
5528         // (unlikely to happen)
5529       }
5530
5531       if (djs == null)
5532       {
5533         /**
5534          * make a new dataset sequence and add it to refIds hash
5535          */
5536         djs = new jalview.datamodel.Sequence(ms.getName(),
5537                 ms.getSequence());
5538         djs.setStart(jmap.getMap().getToLowest());
5539         djs.setEnd(jmap.getMap().getToHighest());
5540         djs.setVamsasId(uniqueSetSuffix + sqid);
5541         jmap.setTo(djs);
5542         incompleteSeqs.put(sqid, djs);
5543         seqRefIds.put(sqid, djs);
5544
5545       }
5546       jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5547       addDBRefs(djs, ms);
5548
5549     }
5550
5551     return jmap;
5552   }
5553
5554   /**
5555    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
5556    * view as XML (but not to file), and then reloading it
5557    * 
5558    * @param ap
5559    * @return
5560    */
5561   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
5562   {
5563     initSeqRefs();
5564     JalviewModel jm = saveState(ap, null, null, null);
5565
5566     addDatasetRef(
5567             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
5568             ap.getAlignment().getDataset());
5569
5570     uniqueSetSuffix = "";
5571     // jm.getJalviewModelSequence().getViewport(0).setId(null);
5572     jm.getViewport().get(0).setId(null);
5573     // we don't overwrite the view we just copied
5574
5575     if (this.frefedSequence == null)
5576     {
5577       frefedSequence = new Vector<>();
5578     }
5579
5580     viewportsAdded.clear();
5581
5582     AlignFrame af = loadFromObject(jm, null, false, null);
5583     af.getAlignPanels().clear();
5584     af.closeMenuItem_actionPerformed(true);
5585
5586     /*
5587      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5588      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5589      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5590      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5591      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5592      */
5593
5594     return af.alignPanel;
5595   }
5596
5597   private Hashtable jvids2vobj;
5598
5599   private void warn(String msg)
5600   {
5601     warn(msg, null);
5602   }
5603
5604   private void warn(String msg, Exception e)
5605   {
5606     if (Cache.log != null)
5607     {
5608       if (e != null)
5609       {
5610         Cache.log.warn(msg, e);
5611       }
5612       else
5613       {
5614         Cache.log.warn(msg);
5615       }
5616     }
5617     else
5618     {
5619       System.err.println("Warning: " + msg);
5620       if (e != null)
5621       {
5622         e.printStackTrace();
5623       }
5624     }
5625   }
5626
5627   private void debug(String string)
5628   {
5629     debug(string, null);
5630   }
5631
5632   private void debug(String msg, Exception e)
5633   {
5634     if (Cache.log != null)
5635     {
5636       if (e != null)
5637       {
5638         Cache.log.debug(msg, e);
5639       }
5640       else
5641       {
5642         Cache.log.debug(msg);
5643       }
5644     }
5645     else
5646     {
5647       System.err.println("Warning: " + msg);
5648       if (e != null)
5649       {
5650         e.printStackTrace();
5651       }
5652     }
5653   }
5654
5655   /**
5656    * set the object to ID mapping tables used to write/recover objects and XML
5657    * ID strings for the jalview project. If external tables are provided then
5658    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5659    * object goes out of scope. - also populates the datasetIds hashtable with
5660    * alignment objects containing dataset sequences
5661    * 
5662    * @param vobj2jv
5663    *          Map from ID strings to jalview datamodel
5664    * @param jv2vobj
5665    *          Map from jalview datamodel to ID strings
5666    * 
5667    * 
5668    */
5669   public void setObjectMappingTables(Hashtable vobj2jv,
5670           IdentityHashMap jv2vobj)
5671   {
5672     this.jv2vobj = jv2vobj;
5673     this.vobj2jv = vobj2jv;
5674     Iterator ds = jv2vobj.keySet().iterator();
5675     String id;
5676     while (ds.hasNext())
5677     {
5678       Object jvobj = ds.next();
5679       id = jv2vobj.get(jvobj).toString();
5680       if (jvobj instanceof jalview.datamodel.Alignment)
5681       {
5682         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5683         {
5684           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5685         }
5686       }
5687       else if (jvobj instanceof jalview.datamodel.Sequence)
5688       {
5689         // register sequence object so the XML parser can recover it.
5690         if (seqRefIds == null)
5691         {
5692           seqRefIds = new HashMap<>();
5693         }
5694         if (seqsToIds == null)
5695         {
5696           seqsToIds = new IdentityHashMap<>();
5697         }
5698         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5699         seqsToIds.put((SequenceI) jvobj, id);
5700       }
5701       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5702       {
5703         String anid;
5704         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5705         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5706         if (jvann.annotationId == null)
5707         {
5708           jvann.annotationId = anid;
5709         }
5710         if (!jvann.annotationId.equals(anid))
5711         {
5712           // TODO verify that this is the correct behaviour
5713           this.warn("Overriding Annotation ID for " + anid
5714                   + " from different id : " + jvann.annotationId);
5715           jvann.annotationId = anid;
5716         }
5717       }
5718       else if (jvobj instanceof String)
5719       {
5720         if (jvids2vobj == null)
5721         {
5722           jvids2vobj = new Hashtable();
5723           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5724         }
5725       }
5726       else
5727       {
5728         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5729       }
5730     }
5731   }
5732
5733   /**
5734    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5735    * objects created from the project archive. If string is null (default for
5736    * construction) then suffix will be set automatically.
5737    * 
5738    * @param string
5739    */
5740   public void setUniqueSetSuffix(String string)
5741   {
5742     uniqueSetSuffix = string;
5743
5744   }
5745
5746   /**
5747    * uses skipList2 as the skipList for skipping views on sequence sets
5748    * associated with keys in the skipList
5749    * 
5750    * @param skipList2
5751    */
5752   public void setSkipList(Hashtable skipList2)
5753   {
5754     skipList = skipList2;
5755   }
5756
5757   /**
5758    * Reads the jar entry of given name and returns its contents, or null if the
5759    * entry is not found.
5760    * 
5761    * @param jprovider
5762    * @param jarEntryName
5763    * @return
5764    */
5765   protected String readJarEntry(jarInputStreamProvider jprovider,
5766           String jarEntryName)
5767   {
5768     String result = null;
5769     BufferedReader in = null;
5770
5771     try
5772     {
5773       /*
5774        * Reopen the jar input stream and traverse its entries to find a matching
5775        * name
5776        */
5777       JarInputStream jin = jprovider.getJarInputStream();
5778       JarEntry entry = null;
5779       do
5780       {
5781         entry = jin.getNextJarEntry();
5782       } while (entry != null && !entry.getName().equals(jarEntryName));
5783
5784       if (entry != null)
5785       {
5786         StringBuilder out = new StringBuilder(256);
5787         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5788         String data;
5789
5790         while ((data = in.readLine()) != null)
5791         {
5792           out.append(data);
5793         }
5794         result = out.toString();
5795       }
5796       else
5797       {
5798         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5799       }
5800     } catch (Exception ex)
5801     {
5802       ex.printStackTrace();
5803     } finally
5804     {
5805       if (in != null)
5806       {
5807         try
5808         {
5809           in.close();
5810         } catch (IOException e)
5811         {
5812           // ignore
5813         }
5814       }
5815     }
5816
5817     return result;
5818   }
5819
5820   /**
5821    * Returns an incrementing counter (0, 1, 2...)
5822    * 
5823    * @return
5824    */
5825   private synchronized int nextCounter()
5826   {
5827     return counter++;
5828   }
5829
5830   /**
5831    * Populates an XML model of the feature colour scheme for one feature type
5832    * 
5833    * @param featureType
5834    * @param fcol
5835    * @return
5836    */
5837   public static Colour marshalColour(
5838           String featureType, FeatureColourI fcol)
5839   {
5840     Colour col = new Colour();
5841     if (fcol.isSimpleColour())
5842     {
5843       col.setRGB(Format.getHexString(fcol.getColour()));
5844     }
5845     else
5846     {
5847       col.setRGB(Format.getHexString(fcol.getMaxColour()));
5848       col.setMin(fcol.getMin());
5849       col.setMax(fcol.getMax());
5850       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
5851       col.setAutoScale(fcol.isAutoScaled());
5852       col.setThreshold(fcol.getThreshold());
5853       col.setColourByLabel(fcol.isColourByLabel());
5854       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
5855               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
5856                       : ThresholdType.NONE));
5857       if (fcol.isColourByAttribute())
5858       {
5859         final String[] attName = fcol.getAttributeName();
5860         col.getAttributeName().add(attName[0]);
5861         if (attName.length > 1)
5862         {
5863           col.getAttributeName().add(attName[1]);
5864         }
5865       }
5866       Color noColour = fcol.getNoColour();
5867       if (noColour == null)
5868       {
5869         col.setNoValueColour(NoValueColour.NONE);
5870       }
5871       else if (noColour == fcol.getMaxColour())
5872       {
5873         col.setNoValueColour(NoValueColour.MAX);
5874       }
5875       else
5876       {
5877         col.setNoValueColour(NoValueColour.MIN);
5878       }
5879     }
5880     col.setName(featureType);
5881     return col;
5882   }
5883
5884   /**
5885    * Populates an XML model of the feature filter(s) for one feature type
5886    * 
5887    * @param firstMatcher
5888    *          the first (or only) match condition)
5889    * @param filter
5890    *          remaining match conditions (if any)
5891    * @param and
5892    *          if true, conditions are and-ed, else or-ed
5893    */
5894   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
5895           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
5896           boolean and)
5897   {
5898     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
5899   
5900     if (filters.hasNext())
5901     {
5902       /*
5903        * compound matcher
5904        */
5905       CompoundMatcher compound = new CompoundMatcher();
5906       compound.setAnd(and);
5907       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
5908               firstMatcher, Collections.emptyIterator(), and);
5909       // compound.addMatcherSet(matcher1);
5910       compound.getMatcherSet().add(matcher1);
5911       FeatureMatcherI nextMatcher = filters.next();
5912       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
5913               nextMatcher, filters, and);
5914       // compound.addMatcherSet(matcher2);
5915       compound.getMatcherSet().add(matcher2);
5916       result.setCompoundMatcher(compound);
5917     }
5918     else
5919     {
5920       /*
5921        * single condition matcher
5922        */
5923       // MatchCondition matcherModel = new MatchCondition();
5924       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
5925       matcherModel.setCondition(
5926               firstMatcher.getMatcher().getCondition().getStableName());
5927       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
5928       if (firstMatcher.isByAttribute())
5929       {
5930         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
5931         // matcherModel.setAttributeName(firstMatcher.getAttribute());
5932         String[] attName = firstMatcher.getAttribute();
5933         matcherModel.getAttributeName().add(attName[0]); // attribute
5934         if (attName.length > 1)
5935         {
5936           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
5937         }
5938       }
5939       else if (firstMatcher.isByLabel())
5940       {
5941         matcherModel.setBy(FilterBy.BY_LABEL);
5942       }
5943       else if (firstMatcher.isByScore())
5944       {
5945         matcherModel.setBy(FilterBy.BY_SCORE);
5946       }
5947       result.setMatchCondition(matcherModel);
5948     }
5949   
5950     return result;
5951   }
5952
5953   /**
5954    * Loads one XML model of a feature filter to a Jalview object
5955    * 
5956    * @param featureType
5957    * @param matcherSetModel
5958    * @return
5959    */
5960   public static FeatureMatcherSetI parseFilter(
5961           String featureType,
5962           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
5963   {
5964     FeatureMatcherSetI result = new FeatureMatcherSet();
5965     try
5966     {
5967       parseFilterConditions(result, matcherSetModel, true);
5968     } catch (IllegalStateException e)
5969     {
5970       // mixing AND and OR conditions perhaps
5971       System.err.println(
5972               String.format("Error reading filter conditions for '%s': %s",
5973                       featureType, e.getMessage()));
5974       // return as much as was parsed up to the error
5975     }
5976   
5977     return result;
5978   }
5979
5980   /**
5981    * Adds feature match conditions to matcherSet as unmarshalled from XML
5982    * (possibly recursively for compound conditions)
5983    * 
5984    * @param matcherSet
5985    * @param matcherSetModel
5986    * @param and
5987    *          if true, multiple conditions are AND-ed, else they are OR-ed
5988    * @throws IllegalStateException
5989    *           if AND and OR conditions are mixed
5990    */
5991   protected static void parseFilterConditions(
5992           FeatureMatcherSetI matcherSet,
5993           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
5994           boolean and)
5995   {
5996     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
5997             .getMatchCondition();
5998     if (mc != null)
5999     {
6000       /*
6001        * single condition
6002        */
6003       FilterBy filterBy = mc.getBy();
6004       Condition cond = Condition.fromString(mc.getCondition());
6005       String pattern = mc.getValue();
6006       FeatureMatcherI matchCondition = null;
6007       if (filterBy == FilterBy.BY_LABEL)
6008       {
6009         matchCondition = FeatureMatcher.byLabel(cond, pattern);
6010       }
6011       else if (filterBy == FilterBy.BY_SCORE)
6012       {
6013         matchCondition = FeatureMatcher.byScore(cond, pattern);
6014   
6015       }
6016       else if (filterBy == FilterBy.BY_ATTRIBUTE)
6017       {
6018         final List<String> attributeName = mc.getAttributeName();
6019         String[] attNames = attributeName
6020                 .toArray(new String[attributeName.size()]);
6021         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
6022                 attNames);
6023       }
6024   
6025       /*
6026        * note this throws IllegalStateException if AND-ing to a 
6027        * previously OR-ed compound condition, or vice versa
6028        */
6029       if (and)
6030       {
6031         matcherSet.and(matchCondition);
6032       }
6033       else
6034       {
6035         matcherSet.or(matchCondition);
6036       }
6037     }
6038     else
6039     {
6040       /*
6041        * compound condition
6042        */
6043       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
6044               .getCompoundMatcher().getMatcherSet();
6045       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
6046       if (matchers.size() == 2)
6047       {
6048         parseFilterConditions(matcherSet, matchers.get(0), anded);
6049         parseFilterConditions(matcherSet, matchers.get(1), anded);
6050       }
6051       else
6052       {
6053         System.err.println("Malformed compound filter condition");
6054       }
6055     }
6056   }
6057
6058   /**
6059    * Loads one XML model of a feature colour to a Jalview object
6060    * 
6061    * @param colourModel
6062    * @return
6063    */
6064   public static FeatureColourI parseColour(Colour colourModel)
6065   {
6066     FeatureColourI colour = null;
6067   
6068     if (colourModel.getMax() != null)
6069     {
6070       Color mincol = null;
6071       Color maxcol = null;
6072       Color noValueColour = null;
6073   
6074       try
6075       {
6076         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
6077         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6078       } catch (Exception e)
6079       {
6080         Cache.log.warn("Couldn't parse out graduated feature color.", e);
6081       }
6082   
6083       NoValueColour noCol = colourModel.getNoValueColour();
6084       if (noCol == NoValueColour.MIN)
6085       {
6086         noValueColour = mincol;
6087       }
6088       else if (noCol == NoValueColour.MAX)
6089       {
6090         noValueColour = maxcol;
6091       }
6092   
6093       colour = new FeatureColour(mincol, maxcol, noValueColour,
6094               safeFloat(colourModel.getMin()),
6095               safeFloat(colourModel.getMax()));
6096       final List<String> attributeName = colourModel.getAttributeName();
6097       String[] attributes = attributeName
6098               .toArray(new String[attributeName.size()]);
6099       if (attributes != null && attributes.length > 0)
6100       {
6101         colour.setAttributeName(attributes);
6102       }
6103       if (colourModel.isAutoScale() != null)
6104       {
6105         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
6106       }
6107       if (colourModel.isColourByLabel() != null)
6108       {
6109         colour.setColourByLabel(
6110                 colourModel.isColourByLabel().booleanValue());
6111       }
6112       if (colourModel.getThreshold() != null)
6113       {
6114         colour.setThreshold(colourModel.getThreshold().floatValue());
6115       }
6116       ThresholdType ttyp = colourModel.getThreshType();
6117       if (ttyp == ThresholdType.ABOVE)
6118       {
6119         colour.setAboveThreshold(true);
6120       }
6121       else if (ttyp == ThresholdType.BELOW)
6122       {
6123         colour.setBelowThreshold(true);
6124       }
6125     }
6126     else
6127     {
6128       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6129       colour = new FeatureColour(color);
6130     }
6131   
6132     return colour;
6133   }
6134 }