X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FJalview2XML.java;h=a4e5785a1a57c310e6bc0aa4d1c8c4d05d7f2d68;hb=296572df6fc3100c1fd97f3714bb2b6628140310;hp=8beb26fa1d26ad49c2f9ebdab5071ab44a8f6552;hpb=be32c14cd8e48fe0a207cd7030cb9cd46f894678;p=jalview.git diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index 8beb26f..a4e5785 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -20,44 +20,9 @@ */ package jalview.gui; -import java.awt.Rectangle; -import java.io.BufferedReader; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.Vector; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; - -import javax.swing.JInternalFrame; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; - -import org.exolab.castor.xml.Unmarshaller; - +import jalview.analysis.Conservation; +import jalview.api.FeatureColourI; +import jalview.api.ViewStyleI; import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; import jalview.datamodel.AlignedCodonFrame; @@ -65,9 +30,13 @@ import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.PDBEntry; +import jalview.datamodel.RnaViewerModel; +import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.datamodel.StructureViewerModel; import jalview.datamodel.StructureViewerModel.StructureData; +import jalview.ext.varna.RnaModel; +import jalview.gui.StructureViewer.ViewerType; import jalview.schemabinding.version2.AlcodMap; import jalview.schemabinding.version2.AlcodonFrame; import jalview.schemabinding.version2.Annotation; @@ -90,6 +59,8 @@ import jalview.schemabinding.version2.OtherData; import jalview.schemabinding.version2.PdbentryItem; import jalview.schemabinding.version2.Pdbids; import jalview.schemabinding.version2.Property; +import jalview.schemabinding.version2.RnaViewer; +import jalview.schemabinding.version2.SecondaryStructure; import jalview.schemabinding.version2.Sequence; import jalview.schemabinding.version2.SequenceSet; import jalview.schemabinding.version2.SequenceSetProperties; @@ -102,7 +73,7 @@ import jalview.schemabinding.version2.Viewport; import jalview.schemes.AnnotationColourGradient; import jalview.schemes.ColourSchemeI; import jalview.schemes.ColourSchemeProperty; -import jalview.schemes.GraduatedColor; +import jalview.schemes.FeatureColour; import jalview.schemes.ResidueColourScheme; import jalview.schemes.ResidueProperties; import jalview.schemes.UserColourScheme; @@ -110,6 +81,7 @@ import jalview.structure.StructureSelectionManager; import jalview.structures.models.AAStructureBindingModel; import jalview.util.MessageManager; import jalview.util.Platform; +import jalview.util.StringUtils; import jalview.util.jarInputStreamProvider; import jalview.viewmodel.AlignmentViewport; import jalview.viewmodel.seqfeatures.FeatureRendererSettings; @@ -121,6 +93,46 @@ import jalview.ws.params.ArgumentI; import jalview.ws.params.AutoCalcSetting; import jalview.ws.params.WsParamSetI; +import java.awt.Color; +import java.awt.Rectangle; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; + +import javax.swing.JInternalFrame; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; + /** * Write out the current jalview desktop state as a Jalview XML stream. * @@ -133,6 +145,15 @@ import jalview.ws.params.WsParamSetI; */ public class Jalview2XML { + private static final String VIEWER_PREFIX = "viewer_"; + + private static final String RNA_PREFIX = "rna_"; + + private static final String UTF_8 = "UTF-8"; + + // use this with nextCounter() to make unique names for entities + private int counter = 0; + /* * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps * of sequence objects are created. @@ -146,7 +167,9 @@ public class Jalview2XML */ Map seqRefIds = null; - Vector frefedSequence = null; + Map incompleteSeqs = null; + + List frefedSequence = null; boolean raiseGUI = true; // whether errors are raised in dialog boxes or not @@ -156,6 +179,12 @@ public class Jalview2XML */ private Map splitFrameCandidates = new HashMap(); + /* + * Map from displayed rna structure models to their saved session state jar + * entry names + */ + private Map rnaSessions = new HashMap(); + /** * create/return unique hash string for sq * @@ -195,6 +224,10 @@ public class Jalview2XML { seqsToIds.clear(); } + if (incompleteSeqs != null) + { + incompleteSeqs.clear(); + } // seqRefIds = null; // seqsToIds = null; } @@ -217,6 +250,14 @@ public class Jalview2XML { seqRefIds = new HashMap(); } + if (incompleteSeqs == null) + { + incompleteSeqs = new HashMap(); + } + if (frefedSequence == null) + { + frefedSequence = new ArrayList(); + } } public Jalview2XML() @@ -228,78 +269,185 @@ public class Jalview2XML this.raiseGUI = raiseGUI; } + /** + * base class for resolving forward references to sequences by their ID + * + * @author jprocter + * + */ + abstract class SeqFref + { + String sref; + + String type; + + public SeqFref(String _sref, String type) + { + sref = _sref; + this.type = type; + } + + public String getSref() + { + return sref; + } + + public SequenceI getSrefSeq() + { + return seqRefIds.get(sref); + } + + public boolean isResolvable() + { + return seqRefIds.get(sref) != null; + } + + public SequenceI getSrefDatasetSeq() + { + SequenceI sq = seqRefIds.get(sref); + if (sq != null) + { + while (sq.getDatasetSequence() != null) + { + sq = sq.getDatasetSequence(); + } + } + return sq; + } + + /** + * @return true if the forward reference was fully resolved + */ + abstract boolean resolve(); + + @Override + public String toString() + { + return type + " reference to " + sref; + } + } + + /** + * create forward reference for a mapping + * + * @param sref + * @param _jmap + * @return + */ + public SeqFref newMappingRef(final String sref, + final jalview.datamodel.Mapping _jmap) + { + SeqFref fref = new SeqFref(sref, "Mapping") + { + public jalview.datamodel.Mapping jmap = _jmap; + + @Override + boolean resolve() + { + SequenceI seq = getSrefDatasetSeq(); + if (seq == null) + { + return false; + } + jmap.setTo(seq); + return true; + } + }; + return fref; + } + + public SeqFref newAlcodMapRef(final String sref, + final AlignedCodonFrame _cf, final jalview.datamodel.Mapping _jmap) + { + + SeqFref fref = new SeqFref(sref, "Codon Frame") + { + AlignedCodonFrame cf = _cf; + + public jalview.datamodel.Mapping mp = _jmap; + + @Override + public boolean isResolvable() + { + return super.isResolvable() && mp.getTo() != null; + }; + + @Override + boolean resolve() + { + SequenceI seq = getSrefDatasetSeq(); + if (seq == null) + { + return false; + } + cf.addMap(seq, mp.getTo(), mp.getMap()); + return true; + } + }; + return fref; + } + public void resolveFrefedSequences() { - if (frefedSequence.size() > 0) + Iterator nextFref = frefedSequence.iterator(); + int toresolve = frefedSequence.size(); + int unresolved = 0, failedtoresolve = 0; + while (nextFref.hasNext()) { - int r = 0, rSize = frefedSequence.size(); - while (r < rSize) + SeqFref ref = nextFref.next(); + if (ref.isResolvable()) { - Object[] ref = (Object[]) frefedSequence.elementAt(r); - if (ref != null) + try { - String sref = (String) ref[0]; - if (seqRefIds.containsKey(sref)) + if (ref.resolve()) { - if (ref[1] instanceof jalview.datamodel.Mapping) - { - SequenceI seq = seqRefIds.get(sref); - while (seq.getDatasetSequence() != null) - { - seq = seq.getDatasetSequence(); - } - ((jalview.datamodel.Mapping) ref[1]).setTo(seq); - } - else - { - if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame) - { - SequenceI seq = seqRefIds.get(sref); - while (seq.getDatasetSequence() != null) - { - seq = seq.getDatasetSequence(); - } - if (ref[2] != null - && ref[2] instanceof jalview.datamodel.Mapping) - { - jalview.datamodel.Mapping mp = (jalview.datamodel.Mapping) ref[2]; - ((jalview.datamodel.AlignedCodonFrame) ref[1]).addMap( - seq, mp.getTo(), mp.getMap()); - } - else - { - System.err - .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for AlcodonFrames involving " - + ref[2].getClass() + " type objects."); - } - } - else - { - System.err - .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for " - + ref[1].getClass() + " type objects."); - } - } - frefedSequence.remove(r); - rSize--; + nextFref.remove(); } else { - System.err - .println("IMPLEMENTATION WARNING: Unresolved forward reference for hash string " - + ref[0] - + " with objecttype " - + ref[1].getClass()); - r++; + failedtoresolve++; } + } catch (Exception x) + { + System.err + .println("IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence " + + ref.getSref()); + x.printStackTrace(); + failedtoresolve++; } - else + } + else + { + unresolved++; + } + } + if (unresolved > 0) + { + System.err.println("Jalview Project Import: There were " + unresolved + + " forward references left unresolved on the stack."); + } + if (failedtoresolve > 0) + { + System.err.println("SERIOUS! " + failedtoresolve + + " resolvable forward references failed to resolve."); + } + if (incompleteSeqs != null && incompleteSeqs.size() > 0) + { + System.err.println("Jalview Project Import: There are " + + incompleteSeqs.size() + + " sequences which may have incomplete metadata."); + if (incompleteSeqs.size() < 10) + { + for (SequenceI s : incompleteSeqs.values()) { - // empty reference - frefedSequence.remove(r); - rSize--; + System.err.println(s.toString()); } } + else + { + System.err + .println("Too many to report. Skipping output of incomplete sequences."); + } } } @@ -365,27 +513,48 @@ public class Jalview2XML */ public void saveState(JarOutputStream jout) { - AlignFrame[] frames = Desktop.getAlignFrames(); // Desktop.desktop.getAllFrames(); + AlignFrame[] frames = Desktop.getAlignFrames(); if (frames == null) { return; } + saveAllFrames(Arrays.asList(frames), jout); + } + /** + * core method for storing state for a set of AlignFrames. + * + * @param frames + * - frames involving all data to be exported (including containing + * splitframes) + * @param jout + * - project output stream + */ + private void saveAllFrames(List frames, JarOutputStream jout) + { Hashtable dsses = new Hashtable(); + /* + * ensure cached data is clear before starting + */ + // todo tidy up seqRefIds, seqsToIds initialisation / reset + rnaSessions.clear(); + splitFrameCandidates.clear(); + try { // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS // ////////////////////////////////////////////////// - Vector shortNames = new Vector(); + List shortNames = new ArrayList(); + List viewIds = new ArrayList(); // REVERSE ORDER - for (int i = frames.length - 1; i > -1; i--) + for (int i = frames.size() - 1; i > -1; i--) { - AlignFrame af = frames[i]; + AlignFrame af = frames.get(i); // skip ? if (skipList != null && skipList @@ -394,33 +563,7 @@ public class Jalview2XML continue; } - String shortName = af.getTitle(); - - if (shortName.indexOf(File.separatorChar) > -1) - { - shortName = shortName.substring(shortName - .lastIndexOf(File.separatorChar) + 1); - } - - int count = 1; - - while (shortNames.contains(shortName)) - { - if (shortName.endsWith("_" + (count - 1))) - { - shortName = shortName.substring(0, shortName.lastIndexOf("_")); - } - - shortName = shortName.concat("_" + count); - count++; - } - - shortNames.addElement(shortName); - - if (!shortName.endsWith(".xml")) - { - shortName = shortName + ".xml"; - } + String shortName = makeFilename(af, shortNames); int ap, apSize = af.alignPanels.size(); @@ -433,7 +576,7 @@ public class Jalview2XML fileName = fileName + ".xml"; } - saveState(apanel, fileName, jout); + saveState(apanel, fileName, jout, viewIds); String dssid = getDatasetIdRef(af.getViewport().getAlignment() .getDataset()); @@ -467,34 +610,67 @@ public class Jalview2XML } } + /** + * Generates a distinct file name, based on the title of the AlignFrame, by + * appending _n for increasing n until an unused name is generated. The new + * name (without its extension) is added to the list. + * + * @param af + * @param namesUsed + * @return the generated name, with .xml extension + */ + protected String makeFilename(AlignFrame af, List namesUsed) + { + String shortName = af.getTitle(); + + if (shortName.indexOf(File.separatorChar) > -1) + { + shortName = shortName.substring(shortName + .lastIndexOf(File.separatorChar) + 1); + } + + int count = 1; + + while (namesUsed.contains(shortName)) + { + if (shortName.endsWith("_" + (count - 1))) + { + shortName = shortName.substring(0, shortName.lastIndexOf("_")); + } + + shortName = shortName.concat("_" + count); + count++; + } + + namesUsed.add(shortName); + + if (!shortName.endsWith(".xml")) + { + shortName = shortName + ".xml"; + } + return shortName; + } + // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW public boolean saveAlignment(AlignFrame af, String jarFile, String fileName) { try { - int ap = 0; - int apSize = af.alignPanels.size(); FileOutputStream fos = new FileOutputStream(jarFile); JarOutputStream jout = new JarOutputStream(fos); - Hashtable dsses = new Hashtable(); - for (AlignmentPanel apanel : af.alignPanels) + List frames = new ArrayList(); + + // resolve splitframes + if (af.getViewport().getCodingComplement() != null) { - String jfileName = apSize == 1 ? fileName : fileName + ap; - ap++; - if (!jfileName.endsWith(".xml")) - { - jfileName = jfileName + ".xml"; - } - saveState(apanel, jfileName, jout); - String dssid = getDatasetIdRef(af.getViewport().getAlignment() - .getDataset()); - if (!dsses.containsKey(dssid)) - { - dsses.put(dssid, af); - } + frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames(); + } + else + { + frames.add(af); } - writeDatasetFor(dsses, fileName, jout); + saveAllFrames(frames, jout); try { jout.flush(); @@ -524,7 +700,7 @@ public class Jalview2XML { jfileName = jfileName + ".xml"; } - saveState(_af.alignPanel, jfileName, true, jout); + saveState(_af.alignPanel, jfileName, true, jout, null); } } @@ -538,13 +714,14 @@ public class Jalview2XML * name of alignment panel written to output stream * @param jout * jar output stream + * @param viewIds * @param out * jar entry name */ public JalviewModel saveState(AlignmentPanel ap, String fileName, - JarOutputStream jout) + JarOutputStream jout, List viewIds) { - return saveState(ap, fileName, false, jout); + return saveState(ap, fileName, false, jout, viewIds); } /** @@ -564,10 +741,15 @@ public class Jalview2XML * jar entry name */ public JalviewModel saveState(AlignmentPanel ap, String fileName, - boolean storeDS, JarOutputStream jout) + boolean storeDS, JarOutputStream jout, List viewIds) { + if (viewIds == null) + { + viewIds = new ArrayList(); + } + initSeqRefs(); - List viewIds = new ArrayList(); + List userColours = new ArrayList(); AlignViewport av = ap.av; @@ -579,11 +761,15 @@ public class Jalview2XML object.setVersion(jalview.bin.Cache.getDefault("VERSION", "Development Build")); - jalview.datamodel.AlignmentI jal = av.getAlignment(); + /** + * rjal is full height alignment, jal is actual alignment with full metadata + * but excludes hidden sequences. + */ + jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal; if (av.hasHiddenRows()) { - jal = jal.getHiddenSequences().getFullAlignment(); + rjal = jal.getHiddenSequences().getFullAlignment(); } SequenceSet vamsasSet = new SequenceSet(); @@ -600,6 +786,7 @@ public class Jalview2XML { // switch jal and the dataset jal = jal.getDataset(); + rjal = jal; } } if (jal.getProperties() != null) @@ -617,40 +804,42 @@ public class Jalview2XML JSeq jseq; Set calcIdSet = new HashSet(); - + // record the set of vamsas sequence XML POJO we create. + HashMap vamsasSetIds = new HashMap(); // SAVE SEQUENCES - String id = ""; - jalview.datamodel.SequenceI jds, jdatasq; - for (int i = 0; i < jal.getHeight(); i++) - { - jds = jal.getSequenceAt(i); - jdatasq = jds.getDatasetSequence() == null ? jds : jds - .getDatasetSequence(); - id = seqHash(jds); - - if (seqRefIds.get(id) != null) - { - // This happens for two reasons: 1. multiple views are being serialised. - // 2. the hashCode has collided with another sequence's code. This DOES - // HAPPEN! (PF00072.15.stk does this) - // JBPNote: Uncomment to debug writing out of files that do not read - // back in due to ArrayOutOfBoundExceptions. - // System.err.println("vamsasSeq backref: "+id+""); - // System.err.println(jds.getName()+" - // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString()); - // System.err.println("Hashcode: "+seqHash(jds)); - // SequenceI rsq = (SequenceI) seqRefIds.get(id + ""); - // System.err.println(rsq.getName()+" - // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString()); - // System.err.println("Hashcode: "+seqHash(rsq)); - } - else + for (final SequenceI jds : rjal.getSequences()) + { + final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds + : jds.getDatasetSequence(); + String id = seqHash(jds); + if (vamsasSetIds.get(id) == null) { - vamsasSeq = createVamsasSequence(id, jds); - vamsasSet.addSequence(vamsasSeq); - seqRefIds.put(id, jds); + if (seqRefIds.get(id) != null && !storeDS) + { + // This happens for two reasons: 1. multiple views are being + // serialised. + // 2. the hashCode has collided with another sequence's code. This + // DOES + // HAPPEN! (PF00072.15.stk does this) + // JBPNote: Uncomment to debug writing out of files that do not read + // back in due to ArrayOutOfBoundExceptions. + // System.err.println("vamsasSeq backref: "+id+""); + // System.err.println(jds.getName()+" + // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString()); + // System.err.println("Hashcode: "+seqHash(jds)); + // SequenceI rsq = (SequenceI) seqRefIds.get(id + ""); + // System.err.println(rsq.getName()+" + // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString()); + // System.err.println("Hashcode: "+seqHash(rsq)); + } + else + { + vamsasSeq = createVamsasSequence(id, jds); + vamsasSet.addSequence(vamsasSeq); + vamsasSetIds.put(id, vamsasSeq); + seqRefIds.put(id, jds); + } } - jseq = new JSeq(); jseq.setStart(jds.getStart()); jseq.setEnd(jds.getEnd()); @@ -662,30 +851,36 @@ public class Jalview2XML // Store any sequences this sequence represents if (av.hasHiddenRows()) { + // use rjal, contains the full height alignment jseq.setHidden(av.getAlignment().getHiddenSequences() .isHidden(jds)); - if (av.isHiddenRepSequence(jal.getSequenceAt(i))) + if (av.isHiddenRepSequence(jds)) { jalview.datamodel.SequenceI[] reps = av - .getRepresentedSequences(jal.getSequenceAt(i)) - .getSequencesInOrder(jal); + .getRepresentedSequences(jds).getSequencesInOrder(rjal); for (int h = 0; h < reps.length; h++) { - if (reps[h] != jal.getSequenceAt(i)) + if (reps[h] != jds) { - jseq.addHiddenSequences(jal.findIndex(reps[h])); + jseq.addHiddenSequences(rjal.findIndex(reps[h])); } } } } + // mark sequence as reference - if it is the reference for this view + if (jal.hasSeqrep()) + { + jseq.setViewreference(jds == jal.getSeqrep()); + } } + // TODO: omit sequence features from each alignment view's XML dump if we + // are storing dataset if (jds.getSequenceFeatures() != null) { - jalview.datamodel.SequenceFeature[] sf = jds - .getSequenceFeatures(); + jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures(); int index = 0; while (index < sf.length) { @@ -710,10 +905,11 @@ public class Jalview2XML if (sf[index].otherDetails != null) { String key; - Enumeration keys = sf[index].otherDetails.keys(); - while (keys.hasMoreElements()) + Iterator keys = sf[index].otherDetails.keySet() + .iterator(); + while (keys.hasNext()) { - key = keys.nextElement().toString(); + key = keys.next(); OtherData keyValue = new OtherData(); keyValue.setKey(key); keyValue.setValue(sf[index].otherDetails.get(key).toString()); @@ -726,16 +922,17 @@ public class Jalview2XML } } - if (jdatasq.getPDBId() != null) + if (jdatasq.getAllPDBEntries() != null) { - Enumeration en = jdatasq.getPDBId().elements(); + Enumeration en = jdatasq.getAllPDBEntries().elements(); while (en.hasMoreElements()) { Pdbids pdb = new Pdbids(); jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en .nextElement(); - pdb.setId(entry.getId()); + String pdbId = entry.getId(); + pdb.setId(pdbId); pdb.setType(entry.getType()); /* @@ -753,6 +950,25 @@ public class Jalview2XML StructureViewerBase viewFrame = (StructureViewerBase) frames[f]; matchedFile = saveStructureState(ap, jds, pdb, entry, viewIds, matchedFile, viewFrame); + /* + * Only store each structure viewer's state once in the project + * jar. First time through only (storeDS==false) + */ + String viewId = viewFrame.getViewId(); + if (!storeDS && !viewIds.contains(viewId)) + { + viewIds.add(viewId); + try + { + String viewerState = viewFrame.getStateInfo(); + writeJarEntry(jout, getViewerJarEntryName(viewId), + viewerState.getBytes()); + } catch (IOException e) + { + System.err.println("Error saving viewer state: " + + e.getMessage()); + } + } } } @@ -769,46 +985,14 @@ public class Jalview2XML pdbfiles = new ArrayList(); } - if (!pdbfiles.contains(entry.getId())) + if (!pdbfiles.contains(pdbId)) { - pdbfiles.add(entry.getId()); - DataInputStream dis = null; - try - { - File file = new File(matchedFile); - if (file.exists() && jout != null) - { - byte[] data = new byte[(int) file.length()]; - jout.putNextEntry(new JarEntry(entry.getId())); - dis = new DataInputStream(new FileInputStream(file)); - dis.readFully(data); - - DataOutputStream dout = new DataOutputStream(jout); - dout.write(data, 0, data.length); - dout.flush(); - jout.closeEntry(); - } - } catch (Exception ex) - { - ex.printStackTrace(); - } finally - { - if (dis != null) - { - try - { - dis.close(); - } catch (IOException e) - { - // ignore - } - } - } - + pdbfiles.add(pdbId); + copyFileToJar(jout, matchedFile, pdbId); } } - if (entry.getProperty() != null) + if (entry.getProperty() != null && !entry.getProperty().isEmpty()) { PdbentryItem item = new PdbentryItem(); Hashtable properties = entry.getProperty(); @@ -828,6 +1012,8 @@ public class Jalview2XML } } + saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS); + jms.addJSeq(jseq); } @@ -836,16 +1022,17 @@ public class Jalview2XML jal = av.getAlignment(); } // SAVE MAPPINGS - if (jal.getCodonFrames() != null) + // FOR DATASET + if (storeDS && jal.getCodonFrames() != null) { - Set jac = jal.getCodonFrames(); + List jac = jal.getCodonFrames(); for (AlignedCodonFrame acf : jac) { AlcodonFrame alc = new AlcodonFrame(); - vamsasSet.addAlcodonFrame(alc); if (acf.getProtMappings() != null && acf.getProtMappings().length > 0) { + boolean hasMap = false; SequenceI[] dnas = acf.getdnaSeqs(); jalview.datamodel.Mapping[] pmaps = acf.getProtMappings(); for (int m = 0; m < pmaps.length; m++) @@ -855,39 +1042,44 @@ public class Jalview2XML alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null, false)); alc.addAlcodMap(alcmap); + hasMap = true; + } + if (hasMap) + { + vamsasSet.addAlcodonFrame(alc); } } - -// { -// AlcodonFrame alc = new AlcodonFrame(); -// vamsasSet.addAlcodonFrame(alc); -// for (int p = 0; p < acf.aaWidth; p++) -// { -// Alcodon cmap = new Alcodon(); -// if (acf.codons[p] != null) -// { -// // Null codons indicate a gapped column in the translated peptide -// // alignment. -// cmap.setPos1(acf.codons[p][0]); -// cmap.setPos2(acf.codons[p][1]); -// cmap.setPos3(acf.codons[p][2]); -// } -// alc.addAlcodon(cmap); -// } -// if (acf.getProtMappings() != null -// && acf.getProtMappings().length > 0) -// { -// SequenceI[] dnas = acf.getdnaSeqs(); -// jalview.datamodel.Mapping[] pmaps = acf.getProtMappings(); -// for (int m = 0; m < pmaps.length; m++) -// { -// AlcodMap alcmap = new AlcodMap(); -// alcmap.setDnasq(seqHash(dnas[m])); -// alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null, -// false)); -// alc.addAlcodMap(alcmap); -// } -// } + // TODO: delete this ? dead code from 2.8.3->2.9 ? + // { + // AlcodonFrame alc = new AlcodonFrame(); + // vamsasSet.addAlcodonFrame(alc); + // for (int p = 0; p < acf.aaWidth; p++) + // { + // Alcodon cmap = new Alcodon(); + // if (acf.codons[p] != null) + // { + // // Null codons indicate a gapped column in the translated peptide + // // alignment. + // cmap.setPos1(acf.codons[p][0]); + // cmap.setPos2(acf.codons[p][1]); + // cmap.setPos3(acf.codons[p][2]); + // } + // alc.addAlcodon(cmap); + // } + // if (acf.getProtMappings() != null + // && acf.getProtMappings().length > 0) + // { + // SequenceI[] dnas = acf.getdnaSeqs(); + // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings(); + // for (int m = 0; m < pmaps.length; m++) + // { + // AlcodMap alcmap = new AlcodMap(); + // alcmap.setDnasq(seqHash(dnas[m])); + // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null, + // false)); + // alc.addAlcodMap(alcmap); + // } + // } } } @@ -935,17 +1127,18 @@ public class Jalview2XML } } } + // SAVE ANNOTATIONS /** * store forward refs from an annotationRow to any groups */ - IdentityHashMap groupRefs = new IdentityHashMap(); + IdentityHashMap groupRefs = new IdentityHashMap(); if (storeDS) { for (SequenceI sq : jal.getSequences()) { // Store annotation on dataset sequences only - jalview.datamodel.AlignmentAnnotation[] aa = sq.getAnnotation(); + AlignmentAnnotation[] aa = sq.getAnnotation(); if (aa != null && aa.length > 0) { storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS, @@ -958,8 +1151,7 @@ public class Jalview2XML if (jal.getAlignmentAnnotation() != null) { // Store the annotation shown on the alignment. - jalview.datamodel.AlignmentAnnotation[] aa = jal - .getAlignmentAnnotation(); + AlignmentAnnotation[] aa = jal.getAlignmentAnnotation(); storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS, vamsasSet); } @@ -971,70 +1163,66 @@ public class Jalview2XML int i = -1; for (jalview.datamodel.SequenceGroup sg : jal.getGroups()) { - groups[++i] = new JGroup(); + JGroup jGroup = new JGroup(); + groups[++i] = jGroup; - groups[i].setStart(sg.getStartRes()); - groups[i].setEnd(sg.getEndRes()); - groups[i].setName(sg.getName()); + jGroup.setStart(sg.getStartRes()); + jGroup.setEnd(sg.getEndRes()); + jGroup.setName(sg.getName()); if (groupRefs.containsKey(sg)) { - // group has references so set it's ID field - groups[i].setId(groupRefs.get(sg).toString()); + // group has references so set its ID field + jGroup.setId(groupRefs.get(sg)); } if (sg.cs != null) { if (sg.cs.conservationApplied()) { - groups[i].setConsThreshold(sg.cs.getConservationInc()); + jGroup.setConsThreshold(sg.cs.getConservationInc()); if (sg.cs instanceof jalview.schemes.UserColourScheme) { - groups[i].setColour(setUserColourScheme(sg.cs, userColours, - jms)); + jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms)); } else { - groups[i] - .setColour(ColourSchemeProperty.getColourName(sg.cs)); + jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs)); } } else if (sg.cs instanceof jalview.schemes.AnnotationColourGradient) { - groups[i].setColour("AnnotationColourGradient"); - groups[i].setAnnotationColours(constructAnnotationColours( + jGroup.setColour("AnnotationColourGradient"); + jGroup.setAnnotationColours(constructAnnotationColours( (jalview.schemes.AnnotationColourGradient) sg.cs, userColours, jms)); } else if (sg.cs instanceof jalview.schemes.UserColourScheme) { - groups[i] - .setColour(setUserColourScheme(sg.cs, userColours, jms)); + jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms)); } else { - groups[i].setColour(ColourSchemeProperty.getColourName(sg.cs)); + jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs)); } - groups[i].setPidThreshold(sg.cs.getThreshold()); + jGroup.setPidThreshold(sg.cs.getThreshold()); } - groups[i].setOutlineColour(sg.getOutlineColour().getRGB()); - groups[i].setDisplayBoxes(sg.getDisplayBoxes()); - groups[i].setDisplayText(sg.getDisplayText()); - groups[i].setColourText(sg.getColourText()); - groups[i].setTextCol1(sg.textColour.getRGB()); - groups[i].setTextCol2(sg.textColour2.getRGB()); - groups[i].setTextColThreshold(sg.thresholdTextColour); - groups[i].setShowUnconserved(sg.getShowNonconserved()); - groups[i].setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus()); - groups[i].setShowConsensusHistogram(sg.isShowConsensusHistogram()); - groups[i].setShowSequenceLogo(sg.isShowSequenceLogo()); - groups[i].setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo()); - for (int s = 0; s < sg.getSize(); s++) + jGroup.setOutlineColour(sg.getOutlineColour().getRGB()); + jGroup.setDisplayBoxes(sg.getDisplayBoxes()); + jGroup.setDisplayText(sg.getDisplayText()); + jGroup.setColourText(sg.getColourText()); + jGroup.setTextCol1(sg.textColour.getRGB()); + jGroup.setTextCol2(sg.textColour2.getRGB()); + jGroup.setTextColThreshold(sg.thresholdTextColour); + jGroup.setShowUnconserved(sg.getShowNonconserved()); + jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus()); + jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram()); + jGroup.setShowSequenceLogo(sg.isShowSequenceLogo()); + jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo()); + for (SequenceI seq : sg.getSequences()) { - jalview.datamodel.Sequence seq = (jalview.datamodel.Sequence) sg - .getSequenceAt(s); - groups[i].addSeq(seqHash(seq)); + jGroup.addSeq(seqHash(seq)); } } @@ -1055,15 +1243,26 @@ public class Jalview2XML view.setViewName(av.viewName); view.setGatheredViews(av.isGatherViewsHere()); - Rectangle position = ap.av.getExplodedGeometry(); - if (position == null) + Rectangle size = ap.av.getExplodedGeometry(); + Rectangle position = size; + if (size == null) { - position = ap.alignFrame.getBounds(); + size = ap.alignFrame.getBounds(); + if (av.getCodingComplement() != null) + { + position = ((SplitFrame) ap.alignFrame.getSplitViewContainer()) + .getBounds(); + } + else + { + position = size; + } } view.setXpos(position.x); view.setYpos(position.y); - view.setWidth(position.width); - view.setHeight(position.height); + + view.setWidth(size.width); + view.setHeight(size.height); view.setStartRes(av.startRes); view.setStartSeq(av.startSeq); @@ -1113,6 +1312,7 @@ public class Jalview2XML view.setFontName(av.font.getName()); view.setFontSize(av.font.getSize()); view.setFontStyle(av.font.getStyle()); + view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna()); view.setRenderGaps(av.isRenderGaps()); view.setShowAnnotation(av.isShowAnnotation()); view.setShowBoxes(av.getShowBoxes()); @@ -1133,7 +1333,7 @@ public class Jalview2XML view.setShowGroupConservation(av.isShowGroupConservation()); view.setShowNPfeatureTooltip(av.isShowNPFeats()); view.setShowDbRefTooltip(av.isShowDBRefs()); - view.setFollowHighlight(av.followHighlight); + view.setFollowHighlight(av.isFollowHighlight()); view.setFollowSelection(av.followSelection); view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus()); if (av.getFeaturesDisplayed() != null) @@ -1144,82 +1344,53 @@ public class Jalview2XML .getFeatureRenderer().getRenderOrder() .toArray(new String[0]); - Vector settingsAdded = new Vector(); - Object gstyle = null; - GraduatedColor gcol = null; + Vector settingsAdded = new Vector(); if (renderOrder != null) { - for (int ro = 0; ro < renderOrder.length; ro++) + for (String featureType : renderOrder) { - gstyle = ap.getSeqPanel().seqCanvas.getFeatureRenderer() - .getFeatureStyle(renderOrder[ro]); + FeatureColourI fcol = ap.getSeqPanel().seqCanvas + .getFeatureRenderer().getFeatureStyle(featureType); Setting setting = new Setting(); - setting.setType(renderOrder[ro]); - if (gstyle instanceof GraduatedColor) + setting.setType(featureType); + if (!fcol.isSimpleColour()) { - gcol = (GraduatedColor) gstyle; - setting.setColour(gcol.getMaxColor().getRGB()); - setting.setMincolour(gcol.getMinColor().getRGB()); - setting.setMin(gcol.getMin()); - setting.setMax(gcol.getMax()); - setting.setColourByLabel(gcol.isColourByLabel()); - setting.setAutoScale(gcol.isAutoScale()); - setting.setThreshold(gcol.getThresh()); - setting.setThreshstate(gcol.getThreshType()); + setting.setColour(fcol.getMaxColour().getRGB()); + setting.setMincolour(fcol.getMinColour().getRGB()); + setting.setMin(fcol.getMin()); + setting.setMax(fcol.getMax()); + setting.setColourByLabel(fcol.isColourByLabel()); + setting.setAutoScale(fcol.isAutoScaled()); + setting.setThreshold(fcol.getThreshold()); + // -1 = No threshold, 0 = Below, 1 = Above + setting.setThreshstate(fcol.isAboveThreshold() ? 1 : (fcol + .isBelowThreshold() ? 0 : -1)); } else { - setting.setColour(ap.getSeqPanel().seqCanvas - .getFeatureRenderer() - .getColour(renderOrder[ro]).getRGB()); + setting.setColour(fcol.getColour().getRGB()); } setting.setDisplay(av.getFeaturesDisplayed().isVisible( - renderOrder[ro])); + featureType)); float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer() - .getOrder(renderOrder[ro]); + .getOrder(featureType); if (rorder > -1) { setting.setOrder(rorder); } fs.addSetting(setting); - settingsAdded.addElement(renderOrder[ro]); + settingsAdded.addElement(featureType); } } - // Make sure we save none displayed feature settings - Iterator en = ap.getSeqPanel().seqCanvas.getFeatureRenderer() - .getFeatureColours().keySet().iterator(); - while (en.hasNext()) - { - String key = en.next().toString(); - if (settingsAdded.contains(key)) - { - continue; - } - - Setting setting = new Setting(); - setting.setType(key); - setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer() - .getColour(key).getRGB()); - - setting.setDisplay(false); - float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer() - .getOrder(key); - if (rorder > -1) - { - setting.setOrder(rorder); - } - fs.addSetting(setting); - settingsAdded.addElement(key); - } // is groups actually supposed to be a map here ? - en = ap.getSeqPanel().seqCanvas.getFeatureRenderer() - .getFeatureGroups().iterator(); - Vector groupsAdded = new Vector(); + Iterator en = ap.getSeqPanel().seqCanvas + .getFeatureRenderer().getFeatureGroups().iterator(); + Vector groupsAdded = new Vector(); while (en.hasNext()) { - String grp = en.next().toString(); + String grp = en.next(); if (groupsAdded.contains(grp)) { continue; @@ -1233,7 +1404,6 @@ public class Jalview2XML groupsAdded.addElement(grp); } jms.setFeatureSettings(fs); - } if (av.hasHiddenColumns()) @@ -1285,12 +1455,12 @@ public class Jalview2XML // using save and then load try { + System.out.println("Writing jar entry " + fileName); JarEntry entry = new JarEntry(fileName); jout.putNextEntry(entry); PrintWriter pout = new PrintWriter(new OutputStreamWriter(jout, - "UTF-8")); - org.exolab.castor.xml.Marshaller marshaller = new org.exolab.castor.xml.Marshaller( - pout); + UTF_8)); + Marshaller marshaller = new Marshaller(pout); marshaller.marshal(object); pout.flush(); jout.closeEntry(); @@ -1304,6 +1474,165 @@ public class Jalview2XML } /** + * Save any Varna viewers linked to this sequence. Writes an rnaViewer element + * for each viewer, with + *
    + *
  • viewer geometry (position, size, split pane divider location)
  • + *
  • index of the selected structure in the viewer (currently shows gapped + * or ungapped)
  • + *
  • the id of the annotation holding RNA secondary structure
  • + *
  • (currently only one SS is shown per viewer, may be more in future)
  • + *
+ * Varna viewer state is also written out (in native Varna XML) to separate + * project jar entries. A separate entry is written for each RNA structure + * displayed, with the naming convention + *
    + *
  • rna_viewId_sequenceId_annotationId_[gapped|trimmed]
  • + *
+ * + * @param jout + * @param jseq + * @param jds + * @param viewIds + * @param ap + * @param storeDataset + */ + protected void saveRnaViewers(JarOutputStream jout, JSeq jseq, + final SequenceI jds, List viewIds, AlignmentPanel ap, + boolean storeDataset) + { + if (Desktop.desktop == null) + { + return; + } + JInternalFrame[] frames = Desktop.desktop.getAllFrames(); + for (int f = frames.length - 1; f > -1; f--) + { + if (frames[f] instanceof AppVarna) + { + AppVarna varna = (AppVarna) frames[f]; + /* + * link the sequence to every viewer that is showing it and is linked to + * its alignment panel + */ + if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel()) + { + String viewId = varna.getViewId(); + RnaViewer rna = new RnaViewer(); + rna.setViewId(viewId); + rna.setTitle(varna.getTitle()); + rna.setXpos(varna.getX()); + rna.setYpos(varna.getY()); + rna.setWidth(varna.getWidth()); + rna.setHeight(varna.getHeight()); + rna.setDividerLocation(varna.getDividerLocation()); + rna.setSelectedRna(varna.getSelectedIndex()); + jseq.addRnaViewer(rna); + + /* + * Store each Varna panel's state once in the project per sequence. + * First time through only (storeDataset==false) + */ + // boolean storeSessions = false; + // String sequenceViewId = viewId + seqsToIds.get(jds); + // if (!storeDataset && !viewIds.contains(sequenceViewId)) + // { + // viewIds.add(sequenceViewId); + // storeSessions = true; + // } + for (RnaModel model : varna.getModels()) + { + if (model.seq == jds) + { + /* + * VARNA saves each view (sequence or alignment secondary + * structure, gapped or trimmed) as a separate XML file + */ + String jarEntryName = rnaSessions.get(model); + if (jarEntryName == null) + { + + String varnaStateFile = varna.getStateInfo(model.rna); + jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter(); + copyFileToJar(jout, varnaStateFile, jarEntryName); + rnaSessions.put(model, jarEntryName); + } + SecondaryStructure ss = new SecondaryStructure(); + String annotationId = varna.getAnnotation(jds).annotationId; + ss.setAnnotationId(annotationId); + ss.setViewerState(jarEntryName); + ss.setGapped(model.gapped); + ss.setTitle(model.title); + rna.addSecondaryStructure(ss); + } + } + } + } + } + } + + /** + * Copy the contents of a file to a new entry added to the output jar + * + * @param jout + * @param infilePath + * @param jarEntryName + */ + protected void copyFileToJar(JarOutputStream jout, String infilePath, + String jarEntryName) + { + DataInputStream dis = null; + try + { + File file = new File(infilePath); + if (file.exists() && jout != null) + { + dis = new DataInputStream(new FileInputStream(file)); + byte[] data = new byte[(int) file.length()]; + dis.readFully(data); + writeJarEntry(jout, jarEntryName, data); + } + } catch (Exception ex) + { + ex.printStackTrace(); + } finally + { + if (dis != null) + { + try + { + dis.close(); + } catch (IOException e) + { + // ignore + } + } + } + } + + /** + * Write the data to a new entry of given name in the output jar file + * + * @param jout + * @param jarEntryName + * @param data + * @throws IOException + */ + protected void writeJarEntry(JarOutputStream jout, String jarEntryName, + byte[] data) throws IOException + { + if (jout != null) + { + System.out.println("Writing jar entry " + jarEntryName); + jout.putNextEntry(new JarEntry(jarEntryName)); + DataOutputStream dout = new DataOutputStream(jout); + dout.write(data, 0, data.length); + dout.flush(); + jout.closeEntry(); + } + } + + /** * Save the state of a structure viewer * * @param ap @@ -1321,6 +1650,11 @@ public class Jalview2XML String matchedFile, StructureViewerBase viewFrame) { final AAStructureBindingModel bindingModel = viewFrame.getBinding(); + + /* + * Look for any bindings for this viewer to the PDB file of interest + * (including part matches excluding chain id) + */ for (int peid = 0; peid < bindingModel.getPdbCount(); peid++) { final PDBEntry pdbentry = bindingModel.getPdbEntry(peid); @@ -1329,6 +1663,9 @@ public class Jalview2XML && !(entry.getId().length() > 4 && entry.getId() .toLowerCase().startsWith(pdbId.toLowerCase()))) { + /* + * not interested in a binding to a different PDB entry here + */ continue; } if (matchedFile == null) @@ -1346,7 +1683,6 @@ public class Jalview2XML // can get at it if the ID // match is ambiguous (e.g. // 1QIP==1qipA) - String statestring = viewFrame.getStateInfo(); for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++) { @@ -1359,23 +1695,12 @@ public class Jalview2XML state.setYpos(viewFrame.getY()); state.setWidth(viewFrame.getWidth()); state.setHeight(viewFrame.getHeight()); - final String viewId = viewFrame.getViewId(); - state.setViewId(viewId); - state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap)); - state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap)); - state.setColourByJmol(viewFrame.isColouredByViewer()); - /* - * Only store each structure viewer's state once in each XML document. - */ - if (!viewIds.contains(viewId)) - { - viewIds.add(viewId); - state.setContent(statestring.replaceAll("\n", "")); - } - else - { - state.setContent("# duplicate state"); - } + final String viewId = viewFrame.getViewId(); + state.setViewId(viewId); + state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap)); + state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap)); + state.setColourByJmol(viewFrame.isColouredByViewer()); + state.setType(viewFrame.getViewerType().toString()); pdb.addStructureState(state); } } @@ -1410,62 +1735,65 @@ public class Jalview2XML } private void storeAlignmentAnnotation(AlignmentAnnotation[] aa, - IdentityHashMap groupRefs, AlignmentViewport av, - Set calcIdSet, boolean storeDS, SequenceSet vamsasSet) + IdentityHashMap groupRefs, + AlignmentViewport av, Set calcIdSet, boolean storeDS, + SequenceSet vamsasSet) { for (int i = 0; i < aa.length; i++) { Annotation an = new Annotation(); - if (aa[i].annotationId != null) + AlignmentAnnotation annotation = aa[i]; + if (annotation.annotationId != null) { - annotationIds.put(aa[i].annotationId, aa[i]); + annotationIds.put(annotation.annotationId, annotation); } - an.setId(aa[i].annotationId); + an.setId(annotation.annotationId); - an.setVisible(aa[i].visible); + an.setVisible(annotation.visible); - an.setDescription(aa[i].description); + an.setDescription(annotation.description); - if (aa[i].sequenceRef != null) + if (annotation.sequenceRef != null) { - // TODO later annotation sequenceRef should be the XML ID of the - // sequence rather than its display name - an.setSequenceRef(aa[i].sequenceRef.getName()); + // 2.9 JAL-1781 xref on sequence id rather than name + an.setSequenceRef(seqsToIds.get(annotation.sequenceRef)); } - if (aa[i].groupRef != null) + if (annotation.groupRef != null) { - Object groupIdr = groupRefs.get(aa[i].groupRef); + String groupIdr = groupRefs.get(annotation.groupRef); if (groupIdr == null) { // make a locally unique String - groupRefs.put(aa[i].groupRef, + groupRefs.put( + annotation.groupRef, groupIdr = ("" + System.currentTimeMillis() - + aa[i].groupRef.getName() + groupRefs.size())); + + annotation.groupRef.getName() + groupRefs + .size())); } an.setGroupRef(groupIdr.toString()); } // store all visualization attributes for annotation - an.setGraphHeight(aa[i].graphHeight); - an.setCentreColLabels(aa[i].centreColLabels); - an.setScaleColLabels(aa[i].scaleColLabel); - an.setShowAllColLabels(aa[i].showAllColLabels); - an.setBelowAlignment(aa[i].belowAlignment); + an.setGraphHeight(annotation.graphHeight); + an.setCentreColLabels(annotation.centreColLabels); + an.setScaleColLabels(annotation.scaleColLabel); + an.setShowAllColLabels(annotation.showAllColLabels); + an.setBelowAlignment(annotation.belowAlignment); - if (aa[i].graph > 0) + if (annotation.graph > 0) { an.setGraph(true); - an.setGraphType(aa[i].graph); - an.setGraphGroup(aa[i].graphGroup); - if (aa[i].getThreshold() != null) + an.setGraphType(annotation.graph); + an.setGraphGroup(annotation.graphGroup); + if (annotation.getThreshold() != null) { ThresholdLine line = new ThresholdLine(); - line.setLabel(aa[i].getThreshold().label); - line.setValue(aa[i].getThreshold().value); - line.setColour(aa[i].getThreshold().colour.getRGB()); + line.setLabel(annotation.getThreshold().label); + line.setValue(annotation.getThreshold().value); + line.setColour(annotation.getThreshold().colour.getRGB()); an.setThresholdLine(line); } } @@ -1474,78 +1802,78 @@ public class Jalview2XML an.setGraph(false); } - an.setLabel(aa[i].label); + an.setLabel(annotation.label); - if (aa[i] == av.getAlignmentQualityAnnot() - || aa[i] == av.getAlignmentConservationAnnotation() - || aa[i] == av.getAlignmentConsensusAnnotation() - || aa[i].autoCalculated) + if (annotation == av.getAlignmentQualityAnnot() + || annotation == av.getAlignmentConservationAnnotation() + || annotation == av.getAlignmentConsensusAnnotation() + || annotation.autoCalculated) { // new way of indicating autocalculated annotation - - an.setAutoCalculated(aa[i].autoCalculated); + an.setAutoCalculated(annotation.autoCalculated); } - if (aa[i].hasScore()) + if (annotation.hasScore()) { - an.setScore(aa[i].getScore()); + an.setScore(annotation.getScore()); } - if (aa[i].getCalcId() != null) + if (annotation.getCalcId() != null) { - calcIdSet.add(aa[i].getCalcId()); - an.setCalcId(aa[i].getCalcId()); + calcIdSet.add(annotation.getCalcId()); + an.setCalcId(annotation.getCalcId()); } - if (aa[i].hasProperties()) + if (annotation.hasProperties()) { - for (String pr : aa[i].getProperties()) + for (String pr : annotation.getProperties()) { Property prop = new Property(); prop.setName(pr); - prop.setValue(aa[i].getProperty(pr)); + prop.setValue(annotation.getProperty(pr)); an.addProperty(prop); } } AnnotationElement ae; - if (aa[i].annotations != null) + if (annotation.annotations != null) { an.setScoreOnly(false); - for (int a = 0; a < aa[i].annotations.length; a++) + for (int a = 0; a < annotation.annotations.length; a++) { - if ((aa[i] == null) || (aa[i].annotations[a] == null)) + if ((annotation == null) || (annotation.annotations[a] == null)) { continue; } ae = new AnnotationElement(); - if (aa[i].annotations[a].description != null) + if (annotation.annotations[a].description != null) { - ae.setDescription(aa[i].annotations[a].description); + ae.setDescription(annotation.annotations[a].description); } - if (aa[i].annotations[a].displayCharacter != null) + if (annotation.annotations[a].displayCharacter != null) { - ae.setDisplayCharacter(aa[i].annotations[a].displayCharacter); + ae.setDisplayCharacter(annotation.annotations[a].displayCharacter); } - if (!Float.isNaN(aa[i].annotations[a].value)) + if (!Float.isNaN(annotation.annotations[a].value)) { - ae.setValue(aa[i].annotations[a].value); + ae.setValue(annotation.annotations[a].value); } ae.setPosition(a); - if (aa[i].annotations[a].secondaryStructure > ' ') + if (annotation.annotations[a].secondaryStructure > ' ') { - ae.setSecondaryStructure(aa[i].annotations[a].secondaryStructure + ae.setSecondaryStructure(annotation.annotations[a].secondaryStructure + ""); } - if (aa[i].annotations[a].colour != null - && aa[i].annotations[a].colour != java.awt.Color.black) + if (annotation.annotations[a].colour != null + && annotation.annotations[a].colour != java.awt.Color.black) { - ae.setColour(aa[i].annotations[a].colour.getRGB()); + ae.setColour(annotation.annotations[a].colour.getRGB()); } an.addAnnotationElement(ae); - if (aa[i].autoCalculated) + if (annotation.autoCalculated) { // only write one non-null entry into the annotation row - // sufficient to get the visualization attributes necessary to @@ -1558,7 +1886,7 @@ public class Jalview2XML { an.setScoreOnly(true); } - if (!storeDS || (storeDS && !aa[i].autoCalculated)) + if (!storeDS || (storeDS && !annotation.autoCalculated)) { // skip autocalculated annotation - these are only provided for // alignments @@ -1658,8 +1986,8 @@ public class Jalview2XML } } throw new Error(MessageManager.formatMessage( - "error.unsupported_version_calcIdparam", new Object[] - { calcIdParam.toString() })); + "error.unsupported_version_calcIdparam", + new Object[] { calcIdParam.toString() })); } /** @@ -1741,16 +2069,17 @@ public class Jalview2XML if (jds.getDatasetSequence() != null) { vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence())); - if (jds.getDatasetSequence().getDBRef() != null) - { - dbrefs = jds.getDatasetSequence().getDBRef(); - } } else { - vamsasSeq.setDsseqid(id); // so we can tell which sequences really are + // seqId==dsseqid so we can tell which sequences really are // dataset sequences only - dbrefs = jds.getDBRef(); + vamsasSeq.setDsseqid(id); + dbrefs = jds.getDBRefs(); + if (parentseq == null) + { + parentseq = jds; + } } if (dbrefs != null) { @@ -1802,38 +2131,32 @@ public class Jalview2XML if (jmp.getTo() != null) { MappingChoice mpc = new MappingChoice(); - if (recurse - && (parentseq != jmp.getTo() || parentseq - .getDatasetSequence() != jmp.getTo())) + + // check/create ID for the sequence referenced by getTo() + + String jmpid = ""; + SequenceI ps = null; + if (parentseq != jmp.getTo() + && parentseq.getDatasetSequence() != jmp.getTo()) { - mpc.setSequence(createVamsasSequence(false, seqHash(jmp.getTo()), - jmp.getTo(), jds)); + // chaining dbref rather than a handshaking one + jmpid = seqHash(ps = jmp.getTo()); } else { - String jmpid = ""; - SequenceI ps = null; - if (parentseq != jmp.getTo() - && parentseq.getDatasetSequence() != jmp.getTo()) - { - // chaining dbref rather than a handshaking one - jmpid = seqHash(ps = jmp.getTo()); - } - else - { - jmpid = seqHash(ps = parentseq); - } - mpc.setDseqFor(jmpid); - if (!seqRefIds.containsKey(mpc.getDseqFor())) - { - jalview.bin.Cache.log.debug("creatign new DseqFor ID"); - seqRefIds.put(mpc.getDseqFor(), ps); - } - else - { - jalview.bin.Cache.log.debug("reusing DseqFor ID"); - } + jmpid = seqHash(ps = parentseq); + } + mpc.setDseqFor(jmpid); + if (!seqRefIds.containsKey(mpc.getDseqFor())) + { + jalview.bin.Cache.log.debug("creatign new DseqFor ID"); + seqRefIds.put(mpc.getDseqFor(), ps); + } + else + { + jalview.bin.Cache.log.debug("reusing DseqFor ID"); } + mp.setMappingChoice(mpc); } } @@ -1971,6 +2294,7 @@ public class Jalview2XML { SwingUtilities.invokeAndWait(new Runnable() { + @Override public void run() { setLoadingFinishedForNewStructureViewers(); @@ -1978,7 +2302,7 @@ public class Jalview2XML }); } catch (Exception x) { - + System.err.println("Error loading alignment: " + x.getMessage()); } } return af; @@ -2041,14 +2365,10 @@ public class Jalview2XML } if (seqRefIds == null) { - seqRefIds = new HashMap(); - } - if (frefedSequence == null) - { - frefedSequence = new Vector(); + initSeqRefs(); } - AlignFrame af = null, _af = null; + IdentityHashMap importedDatasets = new IdentityHashMap(); Map gatherToThisFrame = new HashMap(); final String file = jprovider.getFilename(); try @@ -2067,7 +2387,7 @@ public class Jalview2XML if (jarentry != null && jarentry.getName().endsWith(".xml")) { - InputStreamReader in = new InputStreamReader(jin, "UTF-8"); + InputStreamReader in = new InputStreamReader(jin, UTF_8); JalviewModel object = new JalviewModel(); Unmarshaller unmar = new Unmarshaller(object); @@ -2076,13 +2396,24 @@ public class Jalview2XML if (true) // !skipViewport(object)) { _af = loadFromObject(object, file, true, jprovider); - if (object.getJalviewModelSequence().getViewportCount() > 0) + if (_af != null + && object.getJalviewModelSequence().getViewportCount() > 0) { - af = _af; - if (af.viewport.isGatherViewsHere()) + if (af == null) { - gatherToThisFrame.put(af.viewport.getSequenceSetId(), af); + // store a reference to the first view + af = _af; } + if (_af.viewport.isGatherViewsHere()) + { + // if this is a gathered view, keep its reference since + // after gathering views, only this frame will remain + af = _af; + gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af); + } + // Save dataset to register mappings once all resolved + importedDatasets.put(af.viewport.getAlignment().getDataset(), + af.viewport.getAlignment().getDataset()); } } entryCount++; @@ -2094,13 +2425,7 @@ public class Jalview2XML } } while (jarentry != null); resolveFrefedSequences(); - } catch (java.io.FileNotFoundException ex) - { - ex.printStackTrace(); - errorMessage = "Couldn't locate Jalview XML file : " + file; - System.err.println("Exception whilst loading jalview XML file : " - + ex + "\n"); - } catch (java.net.UnknownHostException ex) + } catch (IOException ex) { ex.printStackTrace(); errorMessage = "Couldn't locate Jalview XML file : " + file; @@ -2144,11 +2469,6 @@ public class Jalview2XML e.printStackTrace(); } - if (Desktop.instance != null) - { - Desktop.instance.stopLoading(); - } - /* * Regather multiple views (with the same sequence set id) to the frame (if * any) that is flagged as the one to gather to, i.e. convert them to tabbed @@ -2162,11 +2482,24 @@ public class Jalview2XML } restoreSplitFrames(); - + for (AlignmentI ds : importedDatasets.keySet()) + { + if (ds.getCodonFrames() != null) + { + StructureSelectionManager.getStructureSelectionManager( + Desktop.instance).registerMappings(ds.getCodonFrames()); + } + } if (errorMessage != null) { reportErrors(); } + + if (Desktop.instance != null) + { + Desktop.instance.stopLoading(); + } + return af; } @@ -2211,6 +2544,8 @@ public class Jalview2XML SplitFrame sf = createSplitFrame(dnaFrame, af); addedToSplitFrames.add(dnaFrame); addedToSplitFrames.add(af); + dnaFrame.setMenusForViewport(); + af.setMenusForViewport(); if (af.viewport.isGatherViewsHere()) { gatherTo.add(sf); @@ -2227,10 +2562,12 @@ public class Jalview2XML .entrySet()) { AlignFrame af = candidate.getValue(); - if (!addedToSplitFrames.contains(af)) { + if (!addedToSplitFrames.contains(af)) + { Viewport view = candidate.getKey(); Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(), view.getHeight()); + af.setMenusForViewport(); System.err.println("Failed to restore view " + view.getTitle() + " to split frame"); } @@ -2262,7 +2599,19 @@ public class Jalview2XML int width = (int) dnaFrame.getBounds().getWidth(); int height = (int) (dnaFrame.getBounds().getHeight() + proteinFrame.getBounds().getHeight() + 50); + + /* + * SplitFrame location is saved to both enclosed frames + */ + splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY()); Desktop.addInternalFrame(splitFrame, title, width, height); + + /* + * And compute cDNA consensus (couldn't do earlier with consensus as + * mappings were not yet present) + */ + proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel); + return splitFrame; } @@ -2312,13 +2661,62 @@ public class Jalview2XML */ private final boolean updateLocalViews = false; - String loadPDBFile(jarInputStreamProvider jprovider, String pdbId) + /** + * Returns the path to a temporary file holding the PDB file for the given PDB + * id. The first time of asking, searches for a file of that name in the + * Jalview project jar, and copies it to a new temporary file. Any repeat + * requests just return the path to the file previously created. + * + * @param jprovider + * @param pdbId + * @return + */ + String loadPDBFile(jarInputStreamProvider jprovider, String pdbId, + String origFile) { if (alreadyLoadedPDB.containsKey(pdbId)) { return alreadyLoadedPDB.get(pdbId).toString(); } + String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb", + origFile); + if (tempFile != null) + { + alreadyLoadedPDB.put(pdbId, tempFile); + } + return tempFile; + } + + /** + * Copies the jar entry of given name to a new temporary file and returns the + * path to the file, or null if the entry is not found. + * + * @param jprovider + * @param jarEntryName + * @param prefix + * a prefix for the temporary file name, must be at least three + * characters long + * @param origFile + * null or original file - so new file can be given the same suffix + * as the old one + * @return + */ + protected String copyJarEntry(jarInputStreamProvider jprovider, + String jarEntryName, String prefix, String origFile) + { + BufferedReader in = null; + PrintWriter out = null; + String suffix = ".tmp"; + if (origFile == null) + { + origFile = jarEntryName; + } + int sfpos = origFile.lastIndexOf("."); + if (sfpos > -1 && sfpos < (origFile.length() - 3)) + { + suffix = "." + origFile.substring(sfpos + 1); + } try { JarInputStream jin = jprovider.getJarInputStream(); @@ -2332,38 +2730,46 @@ public class Jalview2XML do { entry = jin.getNextJarEntry(); - } while (entry != null && !entry.getName().equals(pdbId)); + } while (entry != null && !entry.getName().equals(jarEntryName)); if (entry != null) { - BufferedReader in = new BufferedReader(new InputStreamReader(jin)); - File outFile = File.createTempFile("jalview_pdb", ".txt"); + in = new BufferedReader(new InputStreamReader(jin, UTF_8)); + File outFile = File.createTempFile(prefix, suffix); outFile.deleteOnExit(); - PrintWriter out = new PrintWriter(new FileOutputStream(outFile)); + out = new PrintWriter(new FileOutputStream(outFile)); String data; while ((data = in.readLine()) != null) { out.println(data); } - try - { - out.flush(); - } catch (Exception foo) - { - } - ; - out.close(); + out.flush(); String t = outFile.getAbsolutePath(); - alreadyLoadedPDB.put(pdbId, t); return t; } else { - warn("Couldn't find PDB file entry in Jalview Jar for " + pdbId); + warn("Couldn't find entry in Jalview Jar for " + jarEntryName); } } catch (Exception ex) { ex.printStackTrace(); + } finally + { + if (in != null) + { + try + { + in.close(); + } catch (IOException e) + { + // ignore + } + } + if (out != null) + { + out.close(); + } } return null; @@ -2416,36 +2822,71 @@ public class Jalview2XML // LOAD SEQUENCES List hiddenSeqs = null; - jalview.datamodel.Sequence jseq; List tmpseqs = new ArrayList(); boolean multipleView = false; - + SequenceI referenceseqForView = null; JSeq[] jseqs = object.getJalviewModelSequence().getJSeq(); int vi = 0; // counter in vamsasSeq array for (int i = 0; i < jseqs.length; i++) { String seqId = jseqs[i].getId(); - if (seqRefIds.get(seqId) != null) + SequenceI tmpSeq = seqRefIds.get(seqId); + if (tmpSeq != null) { - tmpseqs.add(seqRefIds.get(seqId)); - multipleView = true; + if (!incompleteSeqs.containsKey(seqId)) + { + // may not need this check, but keep it for at least 2.9,1 release + if (tmpSeq.getStart() != jseqs[i].getStart() + || tmpSeq.getEnd() != jseqs[i].getEnd()) + { + System.err + .println("Warning JAL-2154 regression: updating start/end for sequence " + + tmpSeq.toString() + " to " + jseqs[i]); + } + } + else + { + incompleteSeqs.remove(seqId); + } + if (vamsasSeq.length > vi && vamsasSeq[vi].getId().equals(seqId)) + { + // most likely we are reading a dataset XML document so + // update from vamsasSeq section of XML for this sequence + tmpSeq.setName(vamsasSeq[vi].getName()); + tmpSeq.setDescription(vamsasSeq[vi].getDescription()); + tmpSeq.setSequence(vamsasSeq[vi].getSequence()); + vi++; + } + else + { + // reading multiple views, so vamsasSeq set is a subset of JSeq + multipleView = true; + } + tmpSeq.setStart(jseqs[i].getStart()); + tmpSeq.setEnd(jseqs[i].getEnd()); + tmpseqs.add(tmpSeq); } else { - jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(), + tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(), vamsasSeq[vi].getSequence()); - jseq.setDescription(vamsasSeq[vi].getDescription()); - jseq.setStart(jseqs[i].getStart()); - jseq.setEnd(jseqs[i].getEnd()); - jseq.setVamsasId(uniqueSetSuffix + seqId); - seqRefIds.put(vamsasSeq[vi].getId(), jseq); - tmpseqs.add(jseq); + tmpSeq.setDescription(vamsasSeq[vi].getDescription()); + tmpSeq.setStart(jseqs[i].getStart()); + tmpSeq.setEnd(jseqs[i].getEnd()); + tmpSeq.setVamsasId(uniqueSetSuffix + seqId); + seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq); + tmpseqs.add(tmpSeq); vi++; } + if (jseqs[i].hasViewreference() && jseqs[i].getViewreference()) + { + referenceseqForView = tmpseqs.get(tmpseqs.size() - 1); + } + if (jseqs[i].getHidden()) { if (hiddenSeqs == null) @@ -2453,9 +2894,8 @@ public class Jalview2XML hiddenSeqs = new ArrayList(); } - hiddenSeqs.add(seqRefIds.get(seqId)); + hiddenSeqs.add(tmpSeq); } - } // / @@ -2464,31 +2904,51 @@ public class Jalview2XML SequenceI[] orderedSeqs = tmpseqs .toArray(new SequenceI[tmpseqs.size()]); - Alignment al = new Alignment(orderedSeqs); - - // / Add the alignment properties - for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++) - { - SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i); - al.setProperty(ssp.getKey(), ssp.getValue()); - } - - // / - // SequenceFeatures are added to the DatasetSequence, - // so we must create or recover the dataset before loading features + AlignmentI al = null; + // so we must create or recover the dataset alignment before going further // /////////////////////////////// if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "") { - // older jalview projects do not have a dataset id. + // older jalview projects do not have a dataset - so creat alignment and + // dataset + al = new Alignment(orderedSeqs); al.setDataset(null); } else { - // recover dataset - passing on flag indicating if this a 'viewless' - // sequence set (a.k.a. a stored dataset for the project) - recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence() - .getViewportCount() == 0); + boolean isdsal = object.getJalviewModelSequence().getViewportCount() == 0; + if (isdsal) + { + // we are importing a dataset record, so + // recover reference to an alignment already materialsed as dataset + al = getDatasetFor(vamsasSet.getDatasetId()); + } + if (al == null) + { + // materialse the alignment + al = new Alignment(orderedSeqs); + } + if (isdsal) + { + addDatasetRef(vamsasSet.getDatasetId(), al); + } + + // finally, verify all data in vamsasSet is actually present in al + // passing on flag indicating if it is actually a stored dataset + recoverDatasetFor(vamsasSet, al, isdsal); + } + + if (referenceseqForView != null) + { + al.setSeqrep(referenceseqForView); + } + // / Add the alignment properties + for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++) + { + SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i); + al.setProperty(ssp.getKey(), ssp.getValue()); } + // /////////////////////////////// Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this?? @@ -2496,6 +2956,12 @@ public class Jalview2XML { // load sequence features, database references and any associated PDB // structures for the alignment + // + // prior to 2.10, this part would only be executed the first time a + // sequence was encountered, but not afterwards. + // now, for 2.10 projects, this is also done if the xml doc includes + // dataset sequences not actually present in any particular view. + // for (int i = 0; i < vamsasSeq.length; i++) { if (jseqs[i].getFeaturesCount() > 0) @@ -2522,13 +2988,17 @@ public class Jalview2XML } } - - al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf); + // adds feature to datasequence's feature set (since Jalview 2.10) + al.getSequenceAt(i).addSequenceFeature(sf); } } if (vamsasSeq[i].getDBRefCount() > 0) { - addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]); + // adds dbrefs to datasequence's set (since Jalview 2.10) + addDBRefs( + al.getSequenceAt(i).getDatasetSequence() == null ? al.getSequenceAt(i) + : al.getSequenceAt(i).getDatasetSequence(), + vamsasSeq[i]); } if (jseqs[i].getPdbidsCount() > 0) { @@ -2537,21 +3007,52 @@ public class Jalview2XML { jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry(); entry.setId(ids[p].getId()); - entry.setType(ids[p].getType()); - if (ids[p].getFile() != null) + if (ids[p].getType() != null) + { + if (PDBEntry.Type.getType(ids[p].getType()) != null) + { + entry.setType(PDBEntry.Type.getType(ids[p].getType())); + } + else + { + entry.setType(PDBEntry.Type.FILE); + } + } + // jprovider is null when executing 'New View' + if (ids[p].getFile() != null && jprovider != null) { if (!pdbloaded.containsKey(ids[p].getFile())) { - entry.setFile(loadPDBFile(jprovider, ids[p].getId())); + entry.setFile(loadPDBFile(jprovider, ids[p].getId(), + ids[p].getFile())); } else { entry.setFile(pdbloaded.get(ids[p].getId()).toString()); } } + if (ids[p].getPdbentryItem() != null) + { + entry.setProperty(new Hashtable()); + for (PdbentryItem item : ids[p].getPdbentryItem()) + { + for (Property pr : item.getProperty()) + { + entry.getProperty().put(pr.getName(), pr.getValue()); + } + } + } StructureSelectionManager.getStructureSelectionManager( Desktop.instance).registerPDBEntry(entry); - al.getSequenceAt(i).getDatasetSequence().addPDBId(entry); + // adds PDBEntry to datasequence's set (since Jalview 2.10) + if (al.getSequenceAt(i).getDatasetSequence() != null) + { + al.getSequenceAt(i).getDatasetSequence().addPDBId(entry); + } + else + { + al.getSequenceAt(i).addPDBId(entry); + } } } } @@ -2580,30 +3081,31 @@ public class Jalview2XML if (maps[m].getMapping() != null) { mapping = addMapping(maps[m].getMapping()); - } - if (dnaseq != null) - { - cf.addMap(dnaseq, mapping.getTo(), mapping.getMap()); - } - else - { - // defer to later - frefedSequence.add(new Object[] - { maps[m].getDnasq(), cf, mapping }); + if (dnaseq != null && mapping.getTo() != null) + { + cf.addMap(dnaseq, mapping.getTo(), mapping.getMap()); + } + else + { + // defer to later + frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf, + mapping)); + } } } + al.addCodonFrame(cf); } - al.addCodonFrame(cf); } } // //////////////////////////////// // LOAD ANNOTATIONS List autoAlan = new ArrayList(); - /** + + /* * store any annotations which forward reference a group's ID */ - Hashtable> groupAnnotRefs = new Hashtable>(); + Map> groupAnnotRefs = new Hashtable>(); if (vamsasSet.getAnnotationCount() > 0) { @@ -2611,39 +3113,42 @@ public class Jalview2XML for (int i = 0; i < an.length; i++) { + Annotation annotation = an[i]; + /** * test if annotation is automatically calculated for this view only */ boolean autoForView = false; - if (an[i].getLabel().equals("Quality") - || an[i].getLabel().equals("Conservation") - || an[i].getLabel().equals("Consensus")) + if (annotation.getLabel().equals("Quality") + || annotation.getLabel().equals("Conservation") + || annotation.getLabel().equals("Consensus")) { // Kludge for pre 2.5 projects which lacked the autocalculated flag autoForView = true; - if (!an[i].hasAutoCalculated()) + if (!annotation.hasAutoCalculated()) { - an[i].setAutoCalculated(true); + annotation.setAutoCalculated(true); } } if (autoForView - || (an[i].hasAutoCalculated() && an[i].isAutoCalculated())) + || (annotation.hasAutoCalculated() && annotation + .isAutoCalculated())) { // remove ID - we don't recover annotation from other views for // view-specific annotation - an[i].setId(null); + annotation.setId(null); } // set visiblity for other annotation in this view - if (an[i].getId() != null - && annotationIds.containsKey(an[i].getId())) + String annotationId = annotation.getId(); + if (annotationId != null && annotationIds.containsKey(annotationId)) { - AlignmentAnnotation jda = annotationIds.get(an[i].getId()); + AlignmentAnnotation jda = annotationIds.get(annotationId); // in principle Visible should always be true for annotation displayed // in multiple views - if (an[i].hasVisible()) + if (annotation.hasVisible()) { - jda.visible = an[i].getVisible(); + jda.visible = annotation.getVisible(); } al.addAnnotation(jda); @@ -2651,11 +3156,11 @@ public class Jalview2XML continue; } // Construct new annotation from model. - AnnotationElement[] ae = an[i].getAnnotationElement(); + AnnotationElement[] ae = annotation.getAnnotationElement(); jalview.datamodel.Annotation[] anot = null; java.awt.Color firstColour = null; int anpos; - if (!an[i].getScoreOnly()) + if (!annotation.getScoreOnly()) { anot = new jalview.datamodel.Annotation[al.getWidth()]; for (int aa = 0; aa < ae.length && aa < anot.length; aa++) @@ -2692,27 +3197,27 @@ public class Jalview2XML } jalview.datamodel.AlignmentAnnotation jaa = null; - if (an[i].getGraph()) + if (annotation.getGraph()) { float llim = 0, hlim = 0; // if (autoForView || an[i].isAutoCalculated()) { // hlim=11f; // } - jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(), - an[i].getDescription(), anot, llim, hlim, - an[i].getGraphType()); + jaa = new jalview.datamodel.AlignmentAnnotation( + annotation.getLabel(), annotation.getDescription(), anot, + llim, hlim, annotation.getGraphType()); - jaa.graphGroup = an[i].getGraphGroup(); + jaa.graphGroup = annotation.getGraphGroup(); jaa._linecolour = firstColour; - if (an[i].getThresholdLine() != null) + if (annotation.getThresholdLine() != null) { - jaa.setThreshold(new jalview.datamodel.GraphLine(an[i] - .getThresholdLine().getValue(), an[i] + jaa.setThreshold(new jalview.datamodel.GraphLine(annotation + .getThresholdLine().getValue(), annotation .getThresholdLine().getLabel(), new java.awt.Color( - an[i].getThresholdLine().getColour()))); + annotation.getThresholdLine().getColour()))); } - if (autoForView || an[i].isAutoCalculated()) + if (autoForView || annotation.isAutoCalculated()) { // Hardwire the symbol display line to ensure that labels for // histograms are displayed @@ -2732,19 +3237,26 @@ public class Jalview2XML jaa.annotationId = an[i].getId(); } // recover sequence association - if (an[i].getSequenceRef() != null) + String sequenceRef = an[i].getSequenceRef(); + if (sequenceRef != null) { - if (al.findName(an[i].getSequenceRef()) != null) + // from 2.9 sequenceRef is to sequence id (JAL-1781) + SequenceI sequence = seqRefIds.get(sequenceRef); + if (sequence == null) + { + // in pre-2.9 projects sequence ref is to sequence name + sequence = al.findName(sequenceRef); + } + if (sequence != null) { - jaa.createSequenceMapping(al.findName(an[i].getSequenceRef()), - 1, true); - al.findName(an[i].getSequenceRef()).addAlignmentAnnotation(jaa); + jaa.createSequenceMapping(sequence, 1, true); + sequence.addAlignmentAnnotation(jaa); } } // and make a note of any group association if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0) { - ArrayList aal = groupAnnotRefs + List aal = groupAnnotRefs .get(an[i].getGroupRef()); if (aal == null) { @@ -2818,37 +3330,37 @@ public class Jalview2XML boolean addAnnotSchemeGroup = false; for (int i = 0; i < groups.length; i++) { + JGroup jGroup = groups[i]; ColourSchemeI cs = null; - - if (groups[i].getColour() != null) + if (jGroup.getColour() != null) { - if (groups[i].getColour().startsWith("ucs")) + if (jGroup.getColour().startsWith("ucs")) { - cs = getUserColourScheme(jms, groups[i].getColour()); + cs = getUserColourScheme(jms, jGroup.getColour()); } - else if (groups[i].getColour().equals("AnnotationColourGradient") - && groups[i].getAnnotationColours() != null) + else if (jGroup.getColour().equals("AnnotationColourGradient") + && jGroup.getAnnotationColours() != null) { addAnnotSchemeGroup = true; cs = null; } else { - cs = ColourSchemeProperty.getColour(al, groups[i].getColour()); + cs = ColourSchemeProperty.getColour(al, jGroup.getColour()); } if (cs != null) { - cs.setThreshold(groups[i].getPidThreshold(), true); + cs.setThreshold(jGroup.getPidThreshold(), true); } } - Vector seqs = new Vector(); + Vector seqs = new Vector(); - for (int s = 0; s < groups[i].getSeqCount(); s++) + for (int s = 0; s < jGroup.getSeqCount(); s++) { - String seqId = groups[i].getSeq(s) + ""; - jalview.datamodel.SequenceI ts = seqRefIds.get(seqId); + String seqId = jGroup.getSeq(s) + ""; + SequenceI ts = seqRefIds.get(seqId); if (ts != null) { @@ -2861,53 +3373,51 @@ public class Jalview2XML continue; } - jalview.datamodel.SequenceGroup sg = new jalview.datamodel.SequenceGroup( - seqs, groups[i].getName(), cs, groups[i].getDisplayBoxes(), - groups[i].getDisplayText(), groups[i].getColourText(), - groups[i].getStart(), groups[i].getEnd()); + SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs, + jGroup.getDisplayBoxes(), jGroup.getDisplayText(), + jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd()); - sg.setOutlineColour(new java.awt.Color(groups[i].getOutlineColour())); + sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour())); - sg.textColour = new java.awt.Color(groups[i].getTextCol1()); - sg.textColour2 = new java.awt.Color(groups[i].getTextCol2()); - sg.setShowNonconserved(groups[i].hasShowUnconserved() ? groups[i] + sg.textColour = new java.awt.Color(jGroup.getTextCol1()); + sg.textColour2 = new java.awt.Color(jGroup.getTextCol2()); + sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup .isShowUnconserved() : false); - sg.thresholdTextColour = groups[i].getTextColThreshold(); - if (groups[i].hasShowConsensusHistogram()) + sg.thresholdTextColour = jGroup.getTextColThreshold(); + if (jGroup.hasShowConsensusHistogram()) { - sg.setShowConsensusHistogram(groups[i].isShowConsensusHistogram()); + sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram()); } ; - if (groups[i].hasShowSequenceLogo()) + if (jGroup.hasShowSequenceLogo()) { - sg.setshowSequenceLogo(groups[i].isShowSequenceLogo()); + sg.setshowSequenceLogo(jGroup.isShowSequenceLogo()); } - if (groups[i].hasNormaliseSequenceLogo()) + if (jGroup.hasNormaliseSequenceLogo()) { - sg.setNormaliseSequenceLogo(groups[i].isNormaliseSequenceLogo()); + sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo()); } - if (groups[i].hasIgnoreGapsinConsensus()) + if (jGroup.hasIgnoreGapsinConsensus()) { - sg.setIgnoreGapsConsensus(groups[i].getIgnoreGapsinConsensus()); + sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus()); } - if (groups[i].getConsThreshold() != 0) + if (jGroup.getConsThreshold() != 0) { - jalview.analysis.Conservation c = new jalview.analysis.Conservation( - "All", ResidueProperties.propHash, 3, + Conservation c = new Conservation("All", 3, sg.getSequences(null), 0, sg.getWidth() - 1); c.calculate(); c.verdict(false, 25); sg.cs.setConservation(c); } - if (groups[i].getId() != null && groupAnnotRefs.size() > 0) + if (jGroup.getId() != null && groupAnnotRefs.size() > 0) { // re-instate unique group/annotation row reference - ArrayList jaal = groupAnnotRefs - .get(groups[i].getId()); + List jaal = groupAnnotRefs.get(jGroup + .getId()); if (jaal != null) { - for (jalview.datamodel.AlignmentAnnotation jaa : jaal) + for (AlignmentAnnotation jaa : jaal) { jaa.groupRef = sg; if (jaa.autoCalculated) @@ -2932,8 +3442,8 @@ public class Jalview2XML if (addAnnotSchemeGroup) { // reconstruct the annotation colourscheme - sg.cs = constructAnnotationColour( - groups[i].getAnnotationColours(), null, al, jms, false); + sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(), + null, al, jms, false); } } } @@ -2982,132 +3492,244 @@ public class Jalview2XML System.err .println("Warning : Collision between sequence set ID string and existing jalview object mapping."); } - + + } + } + /** + * indicate that annotation colours are applied across all groups (pre + * Jalview 2.8.1 behaviour) + */ + boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan( + "2.8.1", object.getVersion()); + + AlignmentPanel ap = null; + boolean isnewview = true; + if (viewId != null) + { + // Check to see if this alignment already has a view id == viewId + jalview.gui.AlignmentPanel views[] = Desktop + .getAlignmentPanels(uniqueSeqSetId); + if (views != null && views.length > 0) + { + for (int v = 0; v < views.length; v++) + { + if (views[v].av.getViewId().equalsIgnoreCase(viewId)) + { + // recover the existing alignpanel, alignframe, viewport + af = views[v].alignFrame; + av = views[v].av; + ap = views[v]; + // TODO: could even skip resetting view settings if we don't want to + // change the local settings from other jalview processes + isnewview = false; + } + } + } + } + + if (isnewview) + { + af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view, + uniqueSeqSetId, viewId, autoAlan); + av = af.viewport; + ap = af.alignPanel; + } + + /* + * Load any trees, PDB structures and viewers + * + * Not done if flag is false (when this method is used for New View) + */ + if (loadTreesAndStructures) + { + loadTrees(jms, view, af, av, ap); + loadPDBStructures(jprovider, jseqs, af, ap); + loadRnaViewers(jprovider, jseqs, ap); + } + // and finally return. + return af; + } + + /** + * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna + * panel is restored from separate jar entries, two (gapped and trimmed) per + * sequence and secondary structure. + * + * Currently each viewer shows just one sequence and structure (gapped and + * trimmed), however this method is designed to support multiple sequences or + * structures in viewers if wanted in future. + * + * @param jprovider + * @param jseqs + * @param ap + */ + private void loadRnaViewers(jarInputStreamProvider jprovider, + JSeq[] jseqs, AlignmentPanel ap) + { + /* + * scan the sequences for references to viewers; create each one the first + * time it is referenced, add Rna models to existing viewers + */ + for (JSeq jseq : jseqs) + { + for (int i = 0; i < jseq.getRnaViewerCount(); i++) + { + RnaViewer viewer = jseq.getRnaViewer(i); + AppVarna appVarna = findOrCreateVarnaViewer(viewer, + uniqueSetSuffix, ap); + + for (int j = 0; j < viewer.getSecondaryStructureCount(); j++) + { + SecondaryStructure ss = viewer.getSecondaryStructure(j); + SequenceI seq = seqRefIds.get(jseq.getId()); + AlignmentAnnotation ann = this.annotationIds.get(ss + .getAnnotationId()); + + /* + * add the structure to the Varna display (with session state copied + * from the jar to a temporary file) + */ + boolean gapped = ss.isGapped(); + String rnaTitle = ss.getTitle(); + String sessionState = ss.getViewerState(); + String tempStateFile = copyJarEntry(jprovider, sessionState, + "varna", null); + RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped); + appVarna.addModelSession(rna, rnaTitle, tempStateFile); + } + appVarna.setInitialSelection(viewer.getSelectedRna()); } } - /** - * indicate that annotation colours are applied across all groups (pre - * Jalview 2.8.1 behaviour) - */ - boolean doGroupAnnColour = isVersionStringLaterThan("2.8.1", - object.getVersion()); + } - AlignmentPanel ap = null; - boolean isnewview = true; - if (viewId != null) + /** + * Locate and return an already instantiated matching AppVarna, or create one + * if not found + * + * @param viewer + * @param viewIdSuffix + * @param ap + * @return + */ + protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer, + String viewIdSuffix, AlignmentPanel ap) + { + /* + * on each load a suffix is appended to the saved viewId, to avoid conflicts + * if load is repeated + */ + String postLoadId = viewer.getViewId() + viewIdSuffix; + for (JInternalFrame frame : getAllFrames()) { - // Check to see if this alignment already has a view id == viewId - jalview.gui.AlignmentPanel views[] = Desktop - .getAlignmentPanels(uniqueSeqSetId); - if (views != null && views.length > 0) + if (frame instanceof AppVarna) { - for (int v = 0; v < views.length; v++) + AppVarna varna = (AppVarna) frame; + if (postLoadId.equals(varna.getViewId())) { - if (views[v].av.getViewId().equalsIgnoreCase(viewId)) - { - // recover the existing alignpanel, alignframe, viewport - af = views[v].alignFrame; - av = views[v].av; - ap = views[v]; - // TODO: could even skip resetting view settings if we don't want to - // change the local settings from other jalview processes - isnewview = false; - } + // this viewer is already instantiated + // could in future here add ap as another 'parent' of the + // AppVarna window; currently just 1-to-many + return varna; } } } - if (isnewview) - { - af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view, - uniqueSeqSetId, viewId, autoAlan); - av = af.viewport; - ap = af.alignPanel; - } - // LOAD TREES - // ///////////////////////////////////// - if (loadTreesAndStructures && jms.getTreeCount() > 0) + /* + * viewer not found - make it + */ + RnaViewerModel model = new RnaViewerModel(postLoadId, + viewer.getTitle(), viewer.getXpos(), viewer.getYpos(), + viewer.getWidth(), viewer.getHeight(), + viewer.getDividerLocation()); + AppVarna varna = new AppVarna(model, ap); + + return varna; + } + + /** + * Load any saved trees + * + * @param jms + * @param view + * @param af + * @param av + * @param ap + */ + protected void loadTrees(JalviewModelSequence jms, Viewport view, + AlignFrame af, AlignViewport av, AlignmentPanel ap) + { + // TODO result of automated refactoring - are all these parameters needed? + try { - try + for (int t = 0; t < jms.getTreeCount(); t++) { - for (int t = 0; t < jms.getTreeCount(); t++) - { - Tree tree = jms.getTree(t); + Tree tree = jms.getTree(t); - TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId()); - if (tp == null) + TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId()); + if (tp == null) + { + tp = af.ShowNewickTree( + new jalview.io.NewickFile(tree.getNewick()), + tree.getTitle(), tree.getWidth(), tree.getHeight(), + tree.getXpos(), tree.getYpos()); + if (tree.getId() != null) { - tp = af.ShowNewickTree( - new jalview.io.NewickFile(tree.getNewick()), - tree.getTitle(), tree.getWidth(), tree.getHeight(), - tree.getXpos(), tree.getYpos()); - if (tree.getId() != null) - { - // perhaps bind the tree id to something ? - } + // perhaps bind the tree id to something ? } - else - { - // update local tree attributes ? - // TODO: should check if tp has been manipulated by user - if so its - // settings shouldn't be modified - tp.setTitle(tree.getTitle()); - tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree - .getWidth(), tree.getHeight())); - tp.av = av; // af.viewport; // TODO: verify 'associate with all - // views' - // works still - tp.treeCanvas.av = av; // af.viewport; - tp.treeCanvas.ap = ap; // af.alignPanel; + } + else + { + // update local tree attributes ? + // TODO: should check if tp has been manipulated by user - if so its + // settings shouldn't be modified + tp.setTitle(tree.getTitle()); + tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree + .getWidth(), tree.getHeight())); + tp.av = av; // af.viewport; // TODO: verify 'associate with all + // views' + // works still + tp.treeCanvas.av = av; // af.viewport; + tp.treeCanvas.ap = ap; // af.alignPanel; - } - if (tp == null) - { - warn("There was a problem recovering stored Newick tree: \n" - + tree.getNewick()); - continue; - } + } + if (tp == null) + { + warn("There was a problem recovering stored Newick tree: \n" + + tree.getNewick()); + continue; + } - tp.fitToWindow.setState(tree.getFitToWindow()); - tp.fitToWindow_actionPerformed(null); + tp.fitToWindow.setState(tree.getFitToWindow()); + tp.fitToWindow_actionPerformed(null); - if (tree.getFontName() != null) - { - tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree - .getFontStyle(), tree.getFontSize())); - } - else - { - tp.setTreeFont(new java.awt.Font(view.getFontName(), view - .getFontStyle(), tree.getFontSize())); - } + if (tree.getFontName() != null) + { + tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree + .getFontStyle(), tree.getFontSize())); + } + else + { + tp.setTreeFont(new java.awt.Font(view.getFontName(), view + .getFontStyle(), tree.getFontSize())); + } - tp.showPlaceholders(tree.getMarkUnlinked()); - tp.showBootstrap(tree.getShowBootstrap()); - tp.showDistances(tree.getShowDistances()); + tp.showPlaceholders(tree.getMarkUnlinked()); + tp.showBootstrap(tree.getShowBootstrap()); + tp.showDistances(tree.getShowDistances()); - tp.treeCanvas.threshold = tree.getThreshold(); + tp.treeCanvas.threshold = tree.getThreshold(); - if (tree.getCurrentTree()) - { - af.viewport.setCurrentTree(tp.getTree()); - } + if (tree.getCurrentTree()) + { + af.viewport.setCurrentTree(tp.getTree()); } - - } catch (Exception ex) - { - ex.printStackTrace(); } - } - // //LOAD STRUCTURES - if (loadTreesAndStructures) + } catch (Exception ex) { - loadStructures(jprovider, jseqs, af, ap); + ex.printStackTrace(); } - // and finally return. - return af; } /** @@ -3118,7 +3740,7 @@ public class Jalview2XML * @param af * @param ap */ - protected void loadStructures(jarInputStreamProvider jprovider, + protected void loadPDBStructures(jarInputStreamProvider jprovider, JSeq[] jseqs, AlignFrame af, AlignmentPanel ap) { /* @@ -3146,7 +3768,8 @@ public class Jalview2XML // Originally : ids[p].getFile() // : TODO: verify external PDB file recovery still works in normal // jalview project load - jpdb.setFile(loadPDBFile(jprovider, ids[p].getId())); + jpdb.setFile(loadPDBFile(jprovider, ids[p].getId(), + ids[p].getFile())); jpdb.setId(ids[p].getId()); int x = structureState.getXpos(); @@ -3157,7 +3780,8 @@ public class Jalview2XML // Probably don't need to do this anymore... // Desktop.desktop.getComponentAt(x, y); // TODO: NOW: check that this recovers the PDB file correctly. - String pdbFile = loadPDBFile(jprovider, ids[p].getId()); + String pdbFile = loadPDBFile(jprovider, ids[p].getId(), + ids[p].getFile()); jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i] .getId() + ""); if (sviewid == null) @@ -3167,8 +3791,10 @@ public class Jalview2XML } if (!structureViewers.containsKey(sviewid)) { - structureViewers.put(sviewid, new StructureViewerModel(x, y, - width, height, false, false, true)); + structureViewers.put(sviewid, + new StructureViewerModel(x, y, width, height, false, + false, true, structureState.getViewId(), + structureState.getType())); // Legacy pre-2.7 conversion JAL-823 : // do not assume any view has to be linked for colour by // sequence @@ -3238,8 +3864,16 @@ public class Jalview2XML // Instantiate the associated structure views for (Entry entry : structureViewers .entrySet()) + { + try + { + createOrLinkStructureViewer(entry, af, ap, jprovider); + } catch (Exception e) { - createOrLinkStructureViewer(entry, af, ap); + System.err.println("Error loading structure viewer: " + + e.getMessage()); + // failed - try the next one + } } } @@ -3248,12 +3882,13 @@ public class Jalview2XML * @param viewerData * @param af * @param ap + * @param jprovider */ protected void createOrLinkStructureViewer( Entry viewerData, AlignFrame af, - AlignmentPanel ap) + AlignmentPanel ap, jarInputStreamProvider jprovider) { - final StructureViewerModel svattrib = viewerData.getValue(); + final StructureViewerModel stateData = viewerData.getValue(); /* * Search for any viewer windows already open from other alignment views @@ -3263,67 +3898,80 @@ public class Jalview2XML if (comp != null) { - linkStructureViewer(ap, comp, svattrib); + linkStructureViewer(ap, comp, stateData); return; } /* - * Pending an XML element for ViewerType, just check if stateData contains - * "chimera" (part of the chimera session filename). + * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry + * "viewer_"+stateData.viewId */ - if (svattrib.getStateData().indexOf("chimera") > -1) + if (ViewerType.CHIMERA.toString().equals(stateData.getType())) { - createChimeraViewer(viewerData, af); + createChimeraViewer(viewerData, af, jprovider); } else { - createJmolViewer(viewerData, af); + /* + * else Jmol (if pre-2.9, stateData contains JMOL state string) + */ + createJmolViewer(viewerData, af, jprovider); } } /** * Create a new Chimera viewer. * - * @param viewerData + * @param data * @param af + * @param jprovider */ protected void createChimeraViewer( - Entry viewerData, AlignFrame af) + Entry viewerData, AlignFrame af, + jarInputStreamProvider jprovider) { - final StructureViewerModel data = viewerData.getValue(); - String chimeraSession = data.getStateData(); - - if (new File(chimeraSession).exists()) - { - Set> fileData = data.getFileData() - .entrySet(); - List pdbs = new ArrayList(); - List allseqs = new ArrayList(); - for (Entry pdb : fileData) - { - String filePath = pdb.getValue().getFilePath(); - String pdbId = pdb.getValue().getPdbId(); - pdbs.add(new PDBEntry(filePath, pdbId)); - final List seqList = pdb.getValue().getSeqList(); - SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]); - allseqs.add(seqs); - } + StructureViewerModel data = viewerData.getValue(); + String chimeraSessionFile = data.getStateData(); - boolean colourByChimera = data.isColourByViewer(); - boolean colourBySequence = data.isColourWithAlignPanel(); - - // TODO can/should this be done via StructureViewer (like Jmol)? - final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]); - final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs - .size()][]); - new ChimeraViewFrame(chimeraSession, af.alignPanel, pdbArray, - seqsArray, colourByChimera, colourBySequence); - } - else - { - Cache.log.error("Chimera session file " + chimeraSession - + " not found"); - } + /* + * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file + * + * NB this is the 'saved' viewId as in the project file XML, _not_ the + * 'uniquified' sviewid used to reconstruct the viewer here + */ + String viewerJarEntryName = getViewerJarEntryName(data.getViewId()); + chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName, + "chimera", null); + + Set> fileData = data.getFileData() + .entrySet(); + List pdbs = new ArrayList(); + List allseqs = new ArrayList(); + for (Entry pdb : fileData) + { + String filePath = pdb.getValue().getFilePath(); + String pdbId = pdb.getValue().getPdbId(); + // pdbs.add(new PDBEntry(filePath, pdbId)); + pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath)); + final List seqList = pdb.getValue().getSeqList(); + SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]); + allseqs.add(seqs); + } + + boolean colourByChimera = data.isColourByViewer(); + boolean colourBySequence = data.isColourWithAlignPanel(); + + // TODO use StructureViewer as a factory here, see JAL-1761 + final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]); + final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs + .size()][]); + String newViewId = viewerData.getKey(); + + ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile, + af.alignPanel, pdbArray, seqsArray, colourByChimera, + colourBySequence, newViewId); + cvf.setSize(data.getWidth(), data.getHeight()); + cvf.setLocation(data.getX(), data.getY()); } /** @@ -3333,13 +3981,27 @@ public class Jalview2XML * * @param viewerData * @param af + * @param jprovider */ protected void createJmolViewer( final Entry viewerData, - AlignFrame af) + AlignFrame af, jarInputStreamProvider jprovider) { final StructureViewerModel svattrib = viewerData.getValue(); String state = svattrib.getStateData(); + + /* + * Pre-2.9: state element value is the Jmol state string + * + * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_" + * + viewId + */ + if (ViewerType.JMOL.toString().equals(svattrib.getType())) + { + state = readJarEntry(jprovider, + getViewerJarEntryName(svattrib.getViewId())); + } + List pdbfilenames = new ArrayList(); List seqmaps = new ArrayList(); List pdbids = new ArrayList(); @@ -3360,6 +4022,11 @@ public class Jalview2XML // filename // translation differently. StructureData filedat = oldFiles.get(new File(oldfilenam)); + if (filedat == null) + { + String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\"); + filedat = oldFiles.get(new File(reformatedOldFilename)); + } newFileLoc.append(Platform.escapeString(filedat.getFilePath())); pdbfilenames.add(filedat.getFilePath()); pdbids.add(filedat.getPdbId()); @@ -3396,16 +4063,23 @@ public class Jalview2XML newFileLoc.append(";"); } - if (newFileLoc.length() > 0) + if (newFileLoc.length() == 0) + { + return; + } + int histbug = newFileLoc.indexOf("history = "); + if (histbug > -1) { - int histbug = newFileLoc.indexOf("history = "); + /* + * change "history = [true|false];" to "history = [1|0];" + */ histbug += 10; int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug); String val = (diff == -1) ? null : newFileLoc .substring(histbug, diff); if (val != null && val.length() >= 4) { - if (val.contains("e")) + if (val.contains("e")) // eh? what can it be? { if (val.trim().equals("true")) { @@ -3418,56 +4092,67 @@ public class Jalview2XML newFileLoc.replace(histbug, diff, val); } } + } - final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames - .size()]); - final String[] id = pdbids.toArray(new String[pdbids.size()]); - final SequenceI[][] sq = seqmaps - .toArray(new SequenceI[seqmaps.size()][]); - final String fileloc = newFileLoc.toString(); - final String sviewid = viewerData.getKey(); - final AlignFrame alf = af; - final Rectangle rect = new Rectangle(svattrib.getX(), - svattrib.getY(), svattrib.getWidth(), svattrib.getHeight()); - try + final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames + .size()]); + final String[] id = pdbids.toArray(new String[pdbids.size()]); + final SequenceI[][] sq = seqmaps + .toArray(new SequenceI[seqmaps.size()][]); + final String fileloc = newFileLoc.toString(); + final String sviewid = viewerData.getKey(); + final AlignFrame alf = af; + final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(), + svattrib.getWidth(), svattrib.getHeight()); + try + { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { - javax.swing.SwingUtilities.invokeAndWait(new Runnable() + @Override + public void run() { - @Override - public void run() + JalviewStructureDisplayI sview = null; + try { - JalviewStructureDisplayI sview = null; - try - { - // JAL-1333 note - we probably can't migrate Jmol views to UCSF - // Chimera! - sview = new StructureViewer(alf.alignPanel - .getStructureSelectionManager()).createView( - StructureViewer.ViewerType.JMOL, pdbf, id, sq, - alf.alignPanel, svattrib, fileloc, rect, sviewid); - addNewStructureViewer(sview); - } catch (OutOfMemoryError ex) + sview = new StructureViewer(alf.alignPanel + .getStructureSelectionManager()).createView( + StructureViewer.ViewerType.JMOL, pdbf, id, sq, + alf.alignPanel, svattrib, fileloc, rect, sviewid); + addNewStructureViewer(sview); + } catch (OutOfMemoryError ex) + { + new OOMWarning("restoring structure view for PDB id " + id, + (OutOfMemoryError) ex.getCause()); + if (sview != null && sview.isVisible()) { - new OOMWarning("restoring structure view for PDB id " + id, - (OutOfMemoryError) ex.getCause()); - if (sview != null && sview.isVisible()) - { - sview.closeViewer(); - sview.setVisible(false); - sview.dispose(); - } + sview.closeViewer(false); + sview.setVisible(false); + sview.dispose(); } } - }); - } catch (InvocationTargetException ex) - { - warn("Unexpected error when opening Jmol view.", ex); + } + }); + } catch (InvocationTargetException ex) + { + warn("Unexpected error when opening Jmol view.", ex); - } catch (InterruptedException e) - { - // e.printStackTrace(); - } + } catch (InterruptedException e) + { + // e.printStackTrace(); } + + } + + /** + * Generates a name for the entry in the project jar file to hold state + * information for a structure viewer + * + * @param viewId + * @return + */ + protected String getViewerJarEntryName(String viewId) + { + return VIEWER_PREFIX + viewId; } /** @@ -3496,8 +4181,8 @@ public class Jalview2XML && ((StructureViewerBase) frame).getViewId() .equals(sviewid)) { - comp = (AppJmol) frame; - // todo: break? + comp = (StructureViewerBase) frame; + break; // break added in 2.9 } /* * Otherwise test for matching position and size of viewer frame @@ -3507,8 +4192,8 @@ public class Jalview2XML && frame.getHeight() == svattrib.getHeight() && frame.getWidth() == svattrib.getWidth()) { - comp = (AppJmol) frame; - // todo: break? + comp = (StructureViewerBase) frame; + // no break in faint hope of an exact match on viewId } } } @@ -3526,15 +4211,15 @@ public class Jalview2XML * @param viewerColouring */ protected void linkStructureViewer(AlignmentPanel ap, - StructureViewerBase viewer, StructureViewerModel svattrib) + StructureViewerBase viewer, StructureViewerModel stateData) { // NOTE: if the jalview project is part of a shared session then // view synchronization should/could be done here. - final boolean useinViewerSuperpos = svattrib.isAlignWithPanel(); - final boolean usetoColourbyseq = svattrib.isColourWithAlignPanel(); - final boolean viewerColouring = svattrib.isColourByViewer(); - Map oldFiles = svattrib.getFileData(); + final boolean useinViewerSuperpos = stateData.isAlignWithPanel(); + final boolean usetoColourbyseq = stateData.isColourWithAlignPanel(); + final boolean viewerColouring = stateData.isColourByViewer(); + Map oldFiles = stateData.getFileData(); /* * Add mapping for sequences in this view to an already open viewer @@ -3600,17 +4285,22 @@ public class Jalview2XML } /** + * Answers true if 'version' is equal to or later than 'supported', where each + * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix + * changes. Development and test values for 'version' are leniently treated + * i.e. answer true. * * @param supported * - minimum version we are comparing against * @param version - * - version of data being processsed. - * @return true if version is development/null or evaluates to the same or - * later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*) + * - version of data being processsed + * @return */ - private boolean isVersionStringLaterThan(String supported, String version) + public static boolean isVersionStringLaterThan(String supported, + String version) { - if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD") + if (supported == null || version == null + || version.equalsIgnoreCase("DEVELOPMENT BUILD") || version.equalsIgnoreCase("Test") || version.equalsIgnoreCase("AUTOMATED BUILD")) { @@ -3621,38 +4311,8 @@ public class Jalview2XML } else { - StringTokenizer currentV = new StringTokenizer(supported, "."), fileV = new StringTokenizer( - version, "."); - while (currentV.hasMoreTokens() && fileV.hasMoreTokens()) - { - // convert b to decimal to catch bugfix releases within a series - String curT = currentV.nextToken().toLowerCase().replace('b', '.'); - String fileT = fileV.nextToken().toLowerCase().replace('b', '.'); - try - { - if (Float.valueOf(curT) > Float.valueOf(fileT)) - { - // current version is newer than the version that wrote the file - return false; - } - } catch (NumberFormatException nfe) - { - System.err - .println("** WARNING: Version comparison failed for tokens (" - + curT - + ") and (" - + fileT - + ")\n** Current: '" - + supported + "' and Version: '" + version + "'"); - } - } - if (currentV.hasMoreElements()) - { - // fileV has no minor version but identical series to current - return false; - } + return StringUtils.compareVersions(version, supported, "b") >= 0; } - return true; } Vector newStructureViewers = null; @@ -3680,7 +4340,7 @@ public class Jalview2XML } AlignFrame loadViewport(String file, JSeq[] JSEQ, - List hiddenSeqs, Alignment al, + List hiddenSeqs, AlignmentI al, JalviewModelSequence jms, Viewport view, String uniqueSeqSetId, String viewId, List autoAlan) { @@ -3696,6 +4356,12 @@ public class Jalview2XML .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour())); } + if (al.hasSeqrep()) + { + af.getViewport().setColourByReferenceSeq(true); + af.getViewport().setDisplayReferenceSeq(true); + } + af.viewport.setGatherViewsHere(view.getGatheredViews()); if (view.getSequenceSetId() != null) @@ -3722,25 +4388,25 @@ public class Jalview2XML { for (int s = 0; s < JSEQ.length; s++) { - jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup(); - + SequenceGroup hidden = new SequenceGroup(); + boolean isRepresentative = false; for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++) { - hidden.addSequence( - al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false); + isRepresentative = true; + SequenceI sequenceToHide = al.getSequenceAt(JSEQ[s] + .getHiddenSequences(r)); + hidden.addSequence(sequenceToHide, false); + // remove from hiddenSeqs list so we don't try to hide it twice + hiddenSeqs.remove(sequenceToHide); + } + if (isRepresentative) + { + SequenceI representativeSequence = al.getSequenceAt(s); + hidden.addSequence(representativeSequence, false); + af.viewport.hideRepSequences(representativeSequence, hidden); } - af.viewport.hideRepSequences(al.getSequenceAt(s), hidden); } - // jalview.datamodel.SequenceI[] hseqs = new - // jalview.datamodel.SequenceI[hiddenSeqs - // .size()]; - // - // for (int s = 0; s < hiddenSeqs.size(); s++) - // { - // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s); - // } - SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs .size()]); af.viewport.hideSequence(hseqs); @@ -3766,6 +4432,9 @@ public class Jalview2XML af.viewport.setFont( new java.awt.Font(view.getFontName(), view.getFontStyle(), view .getFontSize()), true); + ViewStyleI vs = af.viewport.getViewStyle(); + vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna()); + af.viewport.setViewStyle(vs); // TODO: allow custom charWidth/Heights to be restored by updating them // after setting font - which means set above to false af.viewport.setRenderGaps(view.getRenderGaps()); @@ -3837,7 +4506,7 @@ public class Jalview2XML } if (view.hasFollowHighlight()) { - af.viewport.followHighlight = view.getFollowHighlight(); + af.viewport.setFollowHighlight(view.getFollowHighlight()); } if (view.hasFollowSelection()) { @@ -3896,25 +4565,33 @@ public class Jalview2XML af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); String[] renderOrder = new String[jms.getFeatureSettings() .getSettingCount()]; - Hashtable featureGroups = new Hashtable(); - Hashtable featureColours = new Hashtable(); - Hashtable featureOrder = new Hashtable(); + Map featureColours = new Hashtable(); + Map featureOrder = new Hashtable(); for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++) { Setting setting = jms.getFeatureSettings().getSetting(fs); if (setting.hasMincolour()) { - GraduatedColor gc = setting.hasMin() ? new GraduatedColor( - new java.awt.Color(setting.getMincolour()), - new java.awt.Color(setting.getColour()), - setting.getMin(), setting.getMax()) : new GraduatedColor( - new java.awt.Color(setting.getMincolour()), - new java.awt.Color(setting.getColour()), 0, 1); + FeatureColourI gc = setting.hasMin() ? new FeatureColour( + new Color(setting.getMincolour()), new Color( + setting.getColour()), setting.getMin(), + setting.getMax()) : new FeatureColour(new Color( + setting.getMincolour()), new Color(setting.getColour()), + 0, 1); if (setting.hasThreshold()) { - gc.setThresh(setting.getThreshold()); - gc.setThreshType(setting.getThreshstate()); + gc.setThreshold(setting.getThreshold()); + int threshstate = setting.getThreshstate(); + // -1 = None, 0 = Below, 1 = Above threshold + if (threshstate == 0) + { + gc.setBelowThreshold(true); + } + else if (threshstate == 1) + { + gc.setAboveThreshold(true); + } } gc.setAutoScaled(true); // default if (setting.hasAutoScale()) @@ -3930,8 +4607,8 @@ public class Jalview2XML } else { - featureColours.put(setting.getType(), - new java.awt.Color(setting.getColour())); + featureColours.put(setting.getType(), new FeatureColour( + new Color(setting.getColour()))); } renderOrder[fs] = setting.getType(); if (setting.hasOrder()) @@ -3948,7 +4625,7 @@ public class Jalview2XML fdi.setVisible(setting.getType()); } } - Hashtable fgtable = new Hashtable(); + Map fgtable = new Hashtable(); for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++) { Group grp = jms.getFeatureSettings().getGroup(gs); @@ -3991,7 +4668,7 @@ public class Jalview2XML } } af.setMenusFromViewport(af.viewport); - + af.setTitle(view.getTitle()); // TODO: we don't need to do this if the viewport is aready visible. /* * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it @@ -4016,7 +4693,7 @@ public class Jalview2XML } private ColourSchemeI constructAnnotationColour( - AnnotationColours viewAnnColour, AlignFrame af, Alignment al, + AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al, JalviewModelSequence jms, boolean checkGroupAnnColour) { boolean propagateAnnColour = false; @@ -4140,7 +4817,7 @@ public class Jalview2XML return cs; } - private void reorderAutoannotation(AlignFrame af, Alignment al, + private void reorderAutoannotation(AlignFrame af, AlignmentI al, List autoAlan) { // copy over visualization settings for autocalculated annotation in the @@ -4150,8 +4827,8 @@ public class Jalview2XML /** * Kludge for magic autoannotation names (see JAL-811) */ - String[] magicNames = new String[] - { "Consensus", "Quality", "Conservation" }; + String[] magicNames = new String[] { "Consensus", "Quality", + "Conservation" }; JvAnnotRow nullAnnot = new JvAnnotRow(-1, null); Hashtable visan = new Hashtable(); for (String nm : magicNames) @@ -4295,10 +4972,11 @@ public class Jalview2XML } } - private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al, + private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al, boolean ignoreUnrefed) { - jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId()); + jalview.datamodel.AlignmentI ds = getDatasetFor(vamsasSet + .getDatasetId()); Vector dseqs = null; if (ds == null) { @@ -4308,7 +4986,7 @@ public class Jalview2XML for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++) { Sequence vamsasSeq = vamsasSet.getSequence(i); - ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed); + ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i); } // create a new dataset if (ds == null) @@ -4335,18 +5013,29 @@ public class Jalview2XML * dataset alignment * @param dseqs * vector to add new dataset sequence to + * @param ignoreUnrefed + * - when true, don't create new sequences from vamsasSeq if it's id + * doesn't already have an asssociated Jalview sequence. + * @param vseqpos + * - used to reorder the sequence in the alignment according to the + * vamsasSeq array ordering, to preserve ordering of dataset */ private void ensureJalviewDatasetSequence(Sequence vamsasSeq, - AlignmentI ds, Vector dseqs, boolean ignoreUnrefed) + AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos) { // JBP TODO: Check this is called for AlCodonFrames to support recovery of // xRef Codon Maps SequenceI sq = seqRefIds.get(vamsasSeq.getId()); + boolean reorder = false; SequenceI dsq = null; if (sq != null && sq.getDatasetSequence() != null) { dsq = sq.getDatasetSequence(); } + else + { + reorder = true; + } if (sq == null && ignoreUnrefed) { return; @@ -4442,31 +5131,64 @@ public class Jalview2XML // + (post ? "appended" : "")); } } + else + { + // sequence refs are identical. We may need to update the existing dataset + // alignment with this one, though. + if (ds != null && dseqs == null) + { + int opos = ds.findIndex(dsq); + SequenceI tseq = null; + if (opos != -1 && vseqpos != opos) + { + // remove from old position + ds.deleteSequence(dsq); + } + if (vseqpos < ds.getHeight()) + { + if (vseqpos != opos) + { + // save sequence at destination position + tseq = ds.getSequenceAt(vseqpos); + ds.replaceSequenceAt(vseqpos, dsq); + ds.addSequence(tseq); + } + } + else + { + ds.addSequence(dsq); + } + } + } } - java.util.Hashtable datasetIds = null; + /* + * TODO use AlignmentI here and in related methods - needs + * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment + */ + Hashtable datasetIds = null; - java.util.IdentityHashMap dataset2Ids = null; + IdentityHashMap dataset2Ids = null; - private Alignment getDatasetFor(String datasetId) + private AlignmentI getDatasetFor(String datasetId) { if (datasetIds == null) { - datasetIds = new Hashtable(); + datasetIds = new Hashtable(); return null; } if (datasetIds.containsKey(datasetId)) { - return (Alignment) datasetIds.get(datasetId); + return datasetIds.get(datasetId); } return null; } - private void addDatasetRef(String datasetId, Alignment dataset) + private void addDatasetRef(String datasetId, AlignmentI dataset) { if (datasetIds == null) { - datasetIds = new Hashtable(); + datasetIds = new Hashtable(); } datasetIds.put(datasetId, dataset); } @@ -4477,7 +5199,7 @@ public class Jalview2XML * @param dataset * @return */ - private String getDatasetIdRef(jalview.datamodel.Alignment dataset) + private String getDatasetIdRef(AlignmentI dataset) { if (dataset.getDataset() != null) { @@ -4489,11 +5211,11 @@ public class Jalview2XML // make a new datasetId and record it if (dataset2Ids == null) { - dataset2Ids = new IdentityHashMap(); + dataset2Ids = new IdentityHashMap(); } else { - datasetId = (String) dataset2Ids.get(dataset); + datasetId = dataset2Ids.get(dataset); } if (datasetId == null) { @@ -4557,8 +5279,7 @@ public class Jalview2XML } else { - frefedSequence.add(new Object[] - { dsfor, jmap }); + frefedSequence.add(newMappingRef(dsfor, jmap)); } } else @@ -4596,6 +5317,7 @@ public class Jalview2XML djs.setEnd(jmap.getMap().getToHighest()); djs.setVamsasId(uniqueSetSuffix + sqid); jmap.setTo(djs); + incompleteSeqs.put(sqid, djs); seqRefIds.put(sqid, djs); } @@ -4612,8 +5334,7 @@ public class Jalview2XML boolean keepSeqRefs) { initSeqRefs(); - jalview.schemabinding.version2.JalviewModel jm = saveState(ap, null, - null); + JalviewModel jm = saveState(ap, null, null, null); if (!keepSeqRefs) { @@ -4833,4 +5554,77 @@ public class Jalview2XML { skipList = skipList2; } + + /** + * Reads the jar entry of given name and returns its contents, or null if the + * entry is not found. + * + * @param jprovider + * @param jarEntryName + * @return + */ + protected String readJarEntry(jarInputStreamProvider jprovider, + String jarEntryName) + { + String result = null; + BufferedReader in = null; + + try + { + /* + * Reopen the jar input stream and traverse its entries to find a matching + * name + */ + JarInputStream jin = jprovider.getJarInputStream(); + JarEntry entry = null; + do + { + entry = jin.getNextJarEntry(); + } while (entry != null && !entry.getName().equals(jarEntryName)); + + if (entry != null) + { + StringBuilder out = new StringBuilder(256); + in = new BufferedReader(new InputStreamReader(jin, UTF_8)); + String data; + + while ((data = in.readLine()) != null) + { + out.append(data); + } + result = out.toString(); + } + else + { + warn("Couldn't find entry in Jalview Jar for " + jarEntryName); + } + } catch (Exception ex) + { + ex.printStackTrace(); + } finally + { + if (in != null) + { + try + { + in.close(); + } catch (IOException e) + { + // ignore + } + } + } + + return result; + } + + /** + * Returns an incrementing counter (0, 1, 2...) + * + * @return + */ + private synchronized int nextCounter() + { + return counter++; + } }