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