JAL-1622 fix bug computing superposition + associated refactorings
[jalview.git] / src / jalview / structures / models / AAStructureBindingModel.java
index 12be239..86c9a21 100644 (file)
@@ -1,17 +1,22 @@
 package jalview.structures.models;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 import jalview.api.StructureSelectionManagerProvider;
+import jalview.datamodel.AlignmentI;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
+import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
+import jalview.util.Comparison;
 import jalview.util.MessageManager;
 
-import java.awt.event.ComponentEvent;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
+ * 
  * A base class to hold common function for protein structure model binding.
  * Initial version created by refactoring JMol and Chimera binding models, but
  * other structure viewers could in principle be accommodated in future.
@@ -45,6 +50,37 @@ public abstract class AAStructureBindingModel extends
 
   protected boolean colourBySequence = true;
 
+  private boolean nucleotide;
+
+  /**
+   * Data bean class to simplify parameterisation in superposeStructures
+   */
+  protected class SuperposeData
+  {
+    /**
+     * Constructor with alignment width argument
+     * 
+     * @param width
+     */
+    public SuperposeData(int width)
+    {
+      pdbResNo = new int[width];
+    }
+
+    public String filename;
+
+    public String pdbId;
+
+    public String chain = "";
+
+    public boolean isRna;
+
+    /*
+     * The pdb residue number (if any) mapped to each column of the alignment
+     */
+    public int[] pdbResNo;
+  }
+
   /**
    * Constructor
    * 
@@ -73,6 +109,7 @@ public abstract class AAStructureBindingModel extends
   {
     this.ssm = ssm;
     this.sequence = sequenceIs;
+    this.nucleotide = Comparison.isNucleotide(sequenceIs);
     this.chains = chains;
     this.pdbEntry = pdbentry;
     this.protocol = protocol;
@@ -99,6 +136,27 @@ public abstract class AAStructureBindingModel extends
   }
 
   /**
+   * Answers true if this binding includes the given PDB id, else false
+   * 
+   * @param pdbId
+   * @return
+   */
+  public boolean hasPdbId(String pdbId)
+  {
+    if (pdbEntry != null)
+    {
+      for (PDBEntry pdb : pdbEntry)
+      {
+        if (pdb.getId().equals(pdbId))
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
    * Returns the number of modelled PDB file entries.
    * 
    * @return
@@ -342,4 +400,205 @@ public abstract class AAStructureBindingModel extends
     addSequenceAndChain(pe, seq, null);
   }
 
+  /**
+   * add the given sequences to the mapping scope for the given pdb file handle
+   * 
+   * @param pdbFile
+   *          - pdbFile identifier
+   * @param seq
+   *          - set of sequences it can be mapped to
+   */
+  public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
+  {
+    for (int pe = 0; pe < getPdbCount(); pe++)
+    {
+      if (getPdbEntry(pe).getFile().equals(pdbFile))
+      {
+        addSequence(pe, seq);
+      }
+    }
+  }
+
+  @Override
+  public abstract void highlightAtoms(List<AtomSpec> atoms);
+
+  protected boolean isNucleotide()
+  {
+    return this.nucleotide;
+  }
+
+  /**
+   * Returns a readable description of all mappings for the wrapped pdbfile to
+   * any mapped sequences
+   * 
+   * @param pdbfile
+   * @param seqs
+   * @return
+   */
+  public String printMappings()
+  {
+    if (pdbEntry == null)
+    {
+      return "";
+    }
+    StringBuilder sb = new StringBuilder(128);
+    for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
+    {
+      String pdbfile = getPdbEntry(pdbe).getFile();
+      List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
+      sb.append(getSsm().printMappings(pdbfile, seqs));
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Returns the mapped structure position for a given aligned column of a given
+   * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
+   * not mapped to structure.
+   * 
+   * @param seq
+   * @param alignedPos
+   * @param mapping
+   * @return
+   */
+  protected int getMappedPosition(SequenceI seq, int alignedPos,
+          StructureMapping mapping)
+  {
+    if (alignedPos >= seq.getLength())
+    {
+      return -1;
+    }
+
+    if (Comparison.isGap(seq.getCharAt(alignedPos)))
+    {
+      return -1;
+    }
+    int seqPos = seq.findPosition(alignedPos);
+    int pos = mapping.getPDBResNum(seqPos);
+    return pos;
+  }
+
+  /**
+   * Helper method to identify residues that can participate in a structure
+   * superposition command. For each structure, identify a sequence in the
+   * alignment which is mapped to the structure. Identify non-gapped columns in
+   * the sequence which have a mapping to a residue in the structure. Returns
+   * the index of the first structure that has a mapping to the alignment.
+   * 
+   * @param alignment
+   *          the sequence alignment which is the basis of structure
+   *          superposition
+   * @param matched
+   *          an array of booleans, indexed by alignment column, where true
+   *          indicates that every structure has a mapped residue present in the
+   *          column (so the column can participate in structure alignment)
+   * @param structures
+   *          an array of data beans corresponding to pdb file index
+   * @return
+   */
+  protected int findSuperposableResidues(AlignmentI alignment,
+          boolean[] matched, SuperposeData[] structures)
+  {
+    int refStructure = -1;
+    String[] files = getPdbFile();
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
+      int lastPos = -1;
+
+      /*
+       * Find the first mapped sequence (if any) for this PDB entry which is in
+       * the alignment
+       */
+      final int seqCountForPdbFile = getSequence()[pdbfnum].length;
+      for (int s = 0; s < seqCountForPdbFile; s++)
+      {
+        for (StructureMapping mapping : mappings)
+        {
+          final SequenceI theSequence = getSequence()[pdbfnum][s];
+          if (mapping.getSequence() == theSequence
+                  && alignment.findIndex(theSequence) > -1)
+          {
+            if (refStructure < 0)
+            {
+              refStructure = pdbfnum;
+            }
+            for (int r = 0; r < matched.length; r++)
+            {
+              if (!matched[r])
+              {
+                continue;
+              }
+              int pos = getMappedPosition(theSequence, r, mapping);
+              if (pos < 1 || pos == lastPos)
+              {
+                matched[r] = false;
+                continue;
+              }
+              lastPos = pos;
+              structures[pdbfnum].pdbResNo[r] = pos;
+            }
+            String chain = mapping.getChain();
+            if (chain != null && chain.trim().length() > 0)
+            {
+              structures[pdbfnum].chain = chain;
+            }
+            structures[pdbfnum].pdbId = mapping.getPdbId();
+            structures[pdbfnum].isRna = theSequence.getRNA() != null;
+            // move on to next pdb file
+            s = seqCountForPdbFile;
+            break;
+          }
+        }
+      }
+    }
+    return refStructure;
+  }
+
+  /**
+   * Returns true if the structure viewer has loaded all of the files of
+   * interest (identified by the file mapping having been set up), or false if
+   * any are still not loaded after a timeout interval.
+   * 
+   * @param files
+   */
+  protected boolean waitForFileLoad(String[] files)
+  {
+    /*
+     * give up after 10 secs plus 1 sec per file
+     */
+    long starttime = System.currentTimeMillis();
+    long endTime = 10000 + 1000 * files.length + starttime;
+    String notLoaded = null;
+
+    boolean waiting = true;
+    while (waiting && System.currentTimeMillis() < endTime)
+    {
+      waiting = false;
+      for (String file : files)
+      {
+        notLoaded = file;
+        try
+        {
+          StructureMapping[] sm = getSsm().getMapping(file);
+          if (sm == null || sm.length == 0)
+          {
+            waiting = true;
+          }
+        } catch (Throwable x)
+        {
+          waiting = true;
+        }
+      }
+    }
+
+    if (waiting)
+    {
+      System.err
+              .println("Timed out waiting for structure viewer to load file "
+                      + notLoaded);
+      return false;
+    }
+    return true;
+  }
 }
\ No newline at end of file