Merge branch 'develop' into features/JAL-1793VCF; lambda Function for
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 24 Oct 2017 12:45:15 +0000 (13:45 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 24 Oct 2017 12:45:15 +0000 (13:45 +0100)
EnsemblLookup parsing

Conflicts:
src/jalview/ext/ensembl/EnsemblInfo.java
src/jalview/ext/ensembl/EnsemblLookup.java
src/jalview/ext/ensembl/EnsemblRestClient.java

1  2 
src/jalview/datamodel/Sequence.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblLookup.java
src/jalview/ext/ensembl/EnsemblRestClient.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/IdPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/SeqPanel.java
test/jalview/gui/SeqCanvasTest.java

Simple merge
@@@ -166,47 -161,9 +166,48 @@@ public class EnsemblGene extends Ensemb
    }
  
    /**
 +   * Parses and saves fields of an Ensembl-style description e.g.
 +   * chromosome:GRCh38:17:45051610:45109016:1
 +   * 
 +   * @param alignment
 +   */
 +  private void parseChromosomeLocations(AlignmentI alignment)
 +  {
 +    for (SequenceI seq : alignment.getSequences())
 +    {
 +      String description = seq.getDescription();
 +      if (description == null)
 +      {
 +        continue;
 +      }
 +      String[] tokens = description.split(":");
 +      if (tokens.length == 6 && tokens[0].startsWith(DBRefEntry.CHROMOSOME))
 +      {
 +        String ref = tokens[1];
 +        String chrom = tokens[2];
 +        try
 +        {
 +          int chStart = Integer.parseInt(tokens[3]);
 +          int chEnd = Integer.parseInt(tokens[4]);
 +          boolean forwardStrand = "1".equals(tokens[5]);
 +          String species = ""; // dunno yet!
 +          int[] from = new int[] { seq.getStart(), seq.getEnd() };
 +          int[] to = new int[] { forwardStrand ? chStart : chEnd,
 +              forwardStrand ? chEnd : chStart };
 +          MapList map = new MapList(from, to, 1, 1);
 +          seq.setGeneLoci(species, ref, chrom, map);
 +        } catch (NumberFormatException e)
 +        {
 +          System.err.println("Bad integers in description " + description);
 +        }
 +      }
 +    }
 +  }
 +
 +  /**
-    * Converts a query, which may contain one or more gene or transcript
-    * identifiers, into a non-redundant list of gene identifiers.
+    * Converts a query, which may contain one or more gene, transcript, or
+    * external (to Ensembl) identifiers, into a non-redundant list of gene
+    * identifiers.
     * 
     * @param accessions
     * @return
@@@ -28,24 -28,28 +28,29 @@@ import java.net.MalformedURLException
  import java.net.URL;
  import java.util.Arrays;
  import java.util.List;
++import java.util.function.Function;
  
  import org.json.simple.JSONObject;
  import org.json.simple.parser.JSONParser;
  import org.json.simple.parser.ParseException;
  
  /**
-- * A client for the Ensembl lookup REST endpoint; used to find the Parent gene
-- * identifier given a transcript identifier.
++ * A client for the Ensembl lookup REST endpoint
   * 
   * @author gmcarstairs
   */
  public class EnsemblLookup extends EnsemblRestClient
  {
 +  private static final String SPECIES = "species";
  
 -  private static final String OBJECT_TYPE_TRANSLATION = "Translation";
    private static final String PARENT = "Parent";
 +
++  private static final String OBJECT_TYPE_TRANSLATION = "Translation";
+   private static final String OBJECT_TYPE_TRANSCRIPT = "Transcript";
+   private static final String ID = "id";
+   private static final String OBJECT_TYPE_GENE = "Gene";
+   private static final String OBJECT_TYPE = "object_type";
    /**
     * Default constructor (to use rest.ensembl.org)
     */
     * @param identifier
     * @return
     */
-   public String getParent(String identifier)
+   public String getGeneId(String identifier)
    {
-     return getAttribute(identifier, PARENT);
++    return getResult(identifier, br -> parseGeneId(br));
 +  }
 +
 +  /**
 +   * Calls the Ensembl lookup REST endpoint and retrieves the 'species' for the
 +   * given identifier, or null if not found
 +   * 
 +   * @param identifier
 +   * @return
 +   */
 +  public String getSpecies(String identifier)
 +  {
-     return getAttribute(identifier, SPECIES);
++    return getResult(identifier, br -> getAttribute(br, SPECIES));
 +  }
 +
 +  /**
 +   * @param identifier
 +   * @param attribute
 +   * @return
 +   */
-   protected String getAttribute(String identifier, String attribute)
++  protected String getResult(String identifier,
++          Function<BufferedReader, String> parser)
 +  {
      List<String> ids = Arrays.asList(new String[] { identifier });
  
      BufferedReader br = null;
        {
          br = getHttpResponse(url, ids);
        }
-       return (parseResponse(br, attribute));
 -      return br == null ? null : parseResponse(br);
++      return br == null ? null : parser.apply(br);
      } catch (IOException e)
      {
        // ignore
    }
  
    /**
-    * Parses the value of 'attribute' from the JSON response and returns the
-    * value, or null if not found
++   * Answers the value of 'attribute' from the JSON response, or null if not
++   * found
 +   * 
 +   * @param br
 +   * @param attribute
 +   * @return
-    * @throws IOException
 +   */
-   protected String parseResponse(BufferedReader br, String attribute) throws IOException
++  protected String getAttribute(BufferedReader br, String attribute)
++  {
++    String value = null;
++    JSONParser jp = new JSONParser();
++    try
++    {
++      JSONObject val = (JSONObject) jp.parse(br);
++      value = val.get(attribute).toString();
++    } catch (ParseException | NullPointerException | IOException e)
++    {
++      // ignore
++    }
++    return value;
++  }
++
++  /**
+    * Parses the JSON response and returns the gene identifier, or null if not
+    * found. If the returned object_type is Gene, returns the id, if Transcript
+    * returns the Parent. If it is Translation (peptide identifier), then the
+    * Parent is the transcript identifier, so we redo the search with this value.
+    * 
+    * @param br
+    * @return
 -   * @throws IOException
+    */
 -  protected String parseResponse(BufferedReader br) throws IOException
++  protected String parseGeneId(BufferedReader br)
    {
-     String parent = null;
+     String geneId = null;
      JSONParser jp = new JSONParser();
      try
      {
        JSONObject val = (JSONObject) jp.parse(br);
-       parent = val.get(attribute).toString();
-     } catch (ParseException | NullPointerException e)
+       String type = val.get(OBJECT_TYPE).toString();
+       if (OBJECT_TYPE_GENE.equalsIgnoreCase(type))
+       {
+         geneId = val.get(ID).toString();
+       }
+       else if (OBJECT_TYPE_TRANSCRIPT.equalsIgnoreCase(type))
+       {
+         geneId = val.get(PARENT).toString();
+       }
+       else if (OBJECT_TYPE_TRANSLATION.equalsIgnoreCase(type))
+       {
+         String transcriptId = val.get(PARENT).toString();
+         try
+         {
+           geneId = getGeneId(transcriptId);
+         } catch (StackOverflowError e)
+         {
+           /*
+            * unlikely data condition error!
+            */
+           System.err
+                   .println("** Ensembl lookup "
+                           + getUrl(transcriptId).toString()
+                           + " looping on Parent!");
+         }
+       }
 -    } catch (ParseException e)
++    } catch (ParseException | IOException e)
      {
        // ignore
      }
@@@ -87,10 -85,10 +85,10 @@@ abstract class EnsemblRestClient extend
  
    static
    {
-     domainData = new HashMap<String, EnsemblData>();
+     domainData = new HashMap<>();
      domainData.put(ENSEMBL_REST,
 -            new EnsemblInfo(ENSEMBL_REST, LATEST_ENSEMBL_REST_VERSION));
 -    domainData.put(ENSEMBL_GENOMES_REST, new EnsemblInfo(
 +            new EnsemblData(ENSEMBL_REST, LATEST_ENSEMBL_REST_VERSION));
 +    domainData.put(ENSEMBL_GENOMES_REST, new EnsemblData(
              ENSEMBL_GENOMES_REST, LATEST_ENSEMBLGENOMES_REST_VERSION));
    }
  
Simple merge
Simple merge
Simple merge
Simple merge
index 0000000,a27bc3f..05b9aea
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,283 +1,281 @@@
+ package jalview.gui;
+ import static org.testng.Assert.assertEquals;
+ import jalview.datamodel.AlignmentI;
+ import jalview.io.DataSourceType;
+ import jalview.io.FileLoader;
+ import java.awt.Font;
+ import java.awt.FontMetrics;
+ import junit.extensions.PA;
+ import org.testng.annotations.Test;
 -import sun.swing.SwingUtilities2;
 -
+ public class SeqCanvasTest
+ {
+   /**
+    * Test the method that computes wrapped width in residues, height of wrapped
+    * widths in pixels, and the number of widths visible
+    */
+   @Test(groups = "Functional")
+   public void testCalculateWrappedGeometry_noAnnotations()
+   {
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             "examples/uniref50.fa", DataSourceType.FILE);
+     AlignViewport av = af.getViewport();
+     AlignmentI al = av.getAlignment();
+     assertEquals(al.getWidth(), 157);
+     assertEquals(al.getHeight(), 15);
+     av.setWrapAlignment(true);
+     av.getRanges().setStartEndSeq(0, 14);
+     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
+     int charHeight = av.getCharHeight();
+     int charWidth = av.getCharWidth();
+     assertEquals(charHeight, 17);
+     assertEquals(charWidth, 12);
+     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
+     /*
+      * first with scales above, left, right
+      */
+     av.setShowAnnotation(false);
+     av.setScaleAboveWrapped(true);
+     av.setScaleLeftWrapped(true);
+     av.setScaleRightWrapped(true);
 -    FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont());
++    FontMetrics fm = testee.getFontMetrics(av.getFont());
+     int labelWidth = fm.stringWidth("000") + charWidth;
+     assertEquals(labelWidth, 39); // 3 x 9 + charWidth
+     /*
+      * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
+      * take the whole multiple of character widths
+      */
+     int canvasWidth = 400;
+     int canvasHeight = 300;
+     int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
+     int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(wrappedWidth, residueColumns);
+     assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
+     assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
+     assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
+             2 * charHeight);
+     int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
+     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+     /*
+      * repeat height is 17 * (2 + 15) = 289
+      * make canvas height 2 * 289 + 3 * charHeight so just enough to
+      * draw 2 widths and the first sequence of a third
+      */
+     canvasHeight = charHeight * (17 * 2 + 3);
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+     /*
+      * reduce canvas height by 1 pixel - should not be enough height
+      * to draw 3 widths
+      */
+     canvasHeight -= 1;
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+     /*
+      * turn off scale above - can now fit in 2 and a bit widths
+      */
+     av.setScaleAboveWrapped(false);
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+     /*
+      * reduce height to enough for 2 widths and not quite a third
+      * i.e. two repeating heights + spacer + sequence - 1 pixel
+      */
+     canvasHeight = charHeight * (16 * 2 + 2) - 1;
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+     /*
+      * make canvas width enough for scales and 20 residues
+      */
+     canvasWidth = 2 * labelWidth + 20 * charWidth;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 20);
+     /*
+      * reduce width by 1 pixel - rounds down to 19 residues
+      */
+     canvasWidth -= 1;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 19);
+     /*
+      * turn off West scale - adds labelWidth (39) to available for residues
+      * which with the 11 remainder makes 50 which is 4 more charWidths rem 2
+      */
+     av.setScaleLeftWrapped(false);
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 23);
+     /*
+      * add 10 pixels to width to fit in another whole residue column
+      */
+     canvasWidth += 9;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 23);
+     canvasWidth += 1;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 24);
+     /*
+      * turn off East scale to gain 39 more pixels (3 columns remainder 3)
+      */
+     av.setScaleRightWrapped(false);
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 27);
+     /*
+      * add 9 pixels to width to gain a residue column
+      */
+     canvasWidth += 8;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 27);
+     canvasWidth += 1;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 28);
+     /*
+      * now West but not East scale - lose 39 pixels or 4 columns
+      */
+     av.setScaleLeftWrapped(true);
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 24);
+     /*
+      * adding 3 pixels to width regains one column
+      */
+     canvasWidth += 2;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 24);
+     canvasWidth += 1;
+     wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+             canvasHeight);
+     assertEquals(wrappedWidth, 25);
+     /*
+      * turn off scales left and right, make width exactly 157 columns
+      */
+     av.setScaleLeftWrapped(false);
+     canvasWidth = al.getWidth() * charWidth;
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+   }
+   /**
+    * Test the method that computes wrapped width in residues, height of wrapped
+    * widths in pixels, and the number of widths visible
+    */
+   @Test(groups = "Functional")
+   public void testCalculateWrappedGeometry_withAnnotations()
+   {
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+             "examples/uniref50.fa", DataSourceType.FILE);
+     AlignViewport av = af.getViewport();
+     AlignmentI al = av.getAlignment();
+     assertEquals(al.getWidth(), 157);
+     assertEquals(al.getHeight(), 15);
+   
+     av.setWrapAlignment(true);
+     av.getRanges().setStartEndSeq(0, 14);
+     av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
+     int charHeight = av.getCharHeight();
+     int charWidth = av.getCharWidth();
+     assertEquals(charHeight, 17);
+     assertEquals(charWidth, 12);
+   
+     SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
+   
+     /*
+      * first with scales above, left, right
+      */
+     av.setShowAnnotation(true);
+     av.setScaleAboveWrapped(true);
+     av.setScaleLeftWrapped(true);
+     av.setScaleRightWrapped(true);
 -    FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont());
++    FontMetrics fm = testee.getFontMetrics(av.getFont());
+     int labelWidth = fm.stringWidth("000") + charWidth;
+     assertEquals(labelWidth, 39); // 3 x 9 + charWidth
+     int annotationHeight = testee.getAnnotationHeight();
+     /*
+      * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
+      * take the whole multiple of character widths
+      */
+     int canvasWidth = 400;
+     int canvasHeight = 300;
+     int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
+     int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(wrappedWidth, residueColumns);
+     assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
+     assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
+     assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
+             2 * charHeight);
+     int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
+     assertEquals(repeatingHeight, charHeight * (2 + al.getHeight())
+             + annotationHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+   
+     /*
+      * repeat height is 17 * (2 + 15) = 289 + annotationHeight = 507
+      * make canvas height 2 * 289 + 3 * charHeight so just enough to
+      * draw 2 widths and the first sequence of a third
+      */
+     canvasHeight = charHeight * (17 * 2 + 3) + 2 * annotationHeight;
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+   
+     /*
+      * reduce canvas height by 1 pixel - should not be enough height
+      * to draw 3 widths
+      */
+     canvasHeight -= 1;
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+   
+     /*
+      * turn off scale above - can now fit in 2 and a bit widths
+      */
+     av.setScaleAboveWrapped(false);
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+   
+     /*
+      * reduce height to enough for 2 widths and not quite a third
+      * i.e. two repeating heights + spacer + sequence - 1 pixel
+      */
+     canvasHeight = charHeight * (16 * 2 + 2) + 2 * annotationHeight - 1;
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+     /*
+      * add 1 pixel to height - should now get 3 widths drawn
+      */
+     canvasHeight += 1;
+     testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+     assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+   }
+ }