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