JAL-3181 order link menu items
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 3 Jan 2019 17:11:02 +0000 (17:11 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 3 Jan 2019 17:11:02 +0000 (17:11 +0000)
src/jalview/gui/PopupMenu.java
src/jalview/urls/CustomUrlProvider.java
src/jalview/util/UrlLink.java
test/jalview/gui/PopupMenuTest.java
test/jalview/util/UrlLinkTest.java

index ed3d29a..473ac3a 100644 (file)
@@ -618,6 +618,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     if (seq != null)
     {
       nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
+      UrlLink.sort(nlinks);
     }
     else
     {
index 86d5660..d23f771 100644 (file)
@@ -107,12 +107,12 @@ public class CustomUrlProvider extends UrlProviderImpl
   {
     // cachedUrlList is in form <label>|<url>|<label>|<url>...
     // parse cachedUrlList into labels (used as id) and url links
-    HashMap<String, UrlLink> urls = new HashMap<String, UrlLink>();
+    HashMap<String, UrlLink> urls = new HashMap<>();
 
     StringTokenizer st = new StringTokenizer(urlStrings, SEP);
     while (st.hasMoreElements())
     {
-      String name = st.nextToken();
+      String name = st.nextToken().trim();
 
       if (!isMiriamId(name))
       {
@@ -129,6 +129,7 @@ public class CustomUrlProvider extends UrlProviderImpl
         {
           url = url + SEP + st.nextToken();
         }
+        url = url.trim();
         urls.put(name, new UrlLink(name, url, name));
       }
     }
@@ -138,7 +139,7 @@ public class CustomUrlProvider extends UrlProviderImpl
 
   private HashMap<String, UrlLink> parseUrlList(Map<String, String> urlList)
   {
-    HashMap<String, UrlLink> urls = new HashMap<String, UrlLink>();
+    HashMap<String, UrlLink> urls = new HashMap<>();
     if (urlList == null)
     {
       return urls;
@@ -187,7 +188,7 @@ public class CustomUrlProvider extends UrlProviderImpl
   @Override
   public List<String> getLinksForMenu()
   {
-    List<String> links = new ArrayList<String>();
+    List<String> links = new ArrayList<>();
     Iterator<Map.Entry<String, UrlLink>> it = selectedUrls.entrySet()
             .iterator();
     while (it.hasNext())
@@ -201,7 +202,7 @@ public class CustomUrlProvider extends UrlProviderImpl
   @Override
   public List<UrlLinkDisplay> getLinksForTable()
   {
-    ArrayList<UrlLinkDisplay> displayLinks = new ArrayList<UrlLinkDisplay>();
+    ArrayList<UrlLinkDisplay> displayLinks = new ArrayList<>();
     displayLinks = getLinksForTable(selectedUrls, true);
     displayLinks.addAll(getLinksForTable(nonselectedUrls, false));
     return displayLinks;
@@ -289,8 +290,8 @@ public class CustomUrlProvider extends UrlProviderImpl
   @Override
   public void setUrlData(List<UrlLinkDisplay> links)
   {
-    HashMap<String, UrlLink> unselurls = new HashMap<String, UrlLink>();
-    HashMap<String, UrlLink> selurls = new HashMap<String, UrlLink>();
+    HashMap<String, UrlLink> unselurls = new HashMap<>();
+    HashMap<String, UrlLink> selurls = new HashMap<>();
 
     Iterator<UrlLinkDisplay> it = links.iterator();
     while (it.hasNext())
index 007da86..dda9e0e 100644 (file)
@@ -29,21 +29,63 @@ import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.SequenceI;
 
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
+/**
+ * A helper class to parse URL Link strings taken from applet parameters or
+ * jalview properties file using the com.stevesoft.pat.Regex implementation.
+ * Jalview 2.4 extension allows regular expressions to be used to parse ID
+ * strings and replace the result in the URL. Regex's operate on the whole ID
+ * string given to the matchURL method, if no regex is supplied, then only text
+ * following the first pipe symbol will be substituted. Usage documentation
+ * todo.
+ */
 public class UrlLink
 {
   /**
-   * helper class to parse URL Link strings taken from applet parameters or
-   * jalview properties file using the com.stevesoft.pat.Regex implementation.
-   * Jalview 2.4 extension allows regular expressions to be used to parse ID
-   * strings and replace the result in the URL. Regex's operate on the whole ID
-   * string given to the matchURL method, if no regex is supplied, then only
-   * text following the first pipe symbol will be substituted. Usage
-   * documentation todo.
+   * A comparator that puts SEQUENCE_ID template links before DB_ACCESSION
+   * links, and otherwise orders by link name (not case sensitive). It expects
+   * to compare strings formatted as "Name|URLTemplate" where the template may
+   * include $SEQUENCE_ID$ or $DB_ACCESSION$ or neither.
    */
+  public static final Comparator<String> LINK_COMPARATOR = new Comparator<String>()
+  {
+    @Override
+    public int compare(String link1, String link2)
+    {
+      if (link1 == null || link2 == null)
+      {
+        return 0; // for failsafe only
+      }
+      String[] tokens1 = link1.split("\\|");
+      String[] tokens2 = link2.split("\\|");
+      if (tokens1.length < 2 || tokens2.length < 2)
+      {
+        // for failsafe only
+        return String.CASE_INSENSITIVE_ORDER.compare(link1, link2);
+      }
+      String name1 = tokens1[0];
+      String name2 = tokens2[0];
+      String pattern1 = tokens1[1];
+      String pattern2 = tokens2[1];
+      if (pattern1.contains(UrlConstants.SEQUENCE_ID)
+              && pattern2.contains(UrlConstants.DB_ACCESSION))
+      {
+        return -1;
+      }
+      if (pattern2.contains(UrlConstants.SEQUENCE_ID)
+              && pattern1.contains(UrlConstants.DB_ACCESSION))
+      {
+        return 1;
+      }
+      return String.CASE_INSENSITIVE_ORDER.compare(name1, name2);
+
+    }
+  };
 
   private static final String EQUALS = "=";
 
@@ -291,7 +333,7 @@ public class UrlLink
                       + rg.stringMatched(s) + "'");
             }
             // try to collate subgroup matches
-            Vector<String> subs = new Vector<String>();
+            Vector<String> subs = new Vector<>();
             // have to loop through submatches, collating them at top level
             // match
             int s = 0; // 1;
@@ -632,4 +674,20 @@ public class UrlLink
       }
     }
   }
+
+  /**
+   * Sorts links (formatted as LinkName|LinkPattern) suitable for display in a
+   * menu
+   * <ul>
+   * <li>SEQUENCE_ID links precede DB_ACCESSION links (i.e. canonical lookup
+   * before cross-references)</li>
+   * <li>otherwise by Link name (case insensitive)</li>
+   * </ul>
+   * 
+   * @param nlinks
+   */
+  public static void sort(List<String> nlinks)
+  {
+    Collections.sort(nlinks, LINK_COMPARATOR);
+  }
 }
index 6f60588..e04be68 100644 (file)
@@ -489,115 +489,82 @@ public class PopupMenuTest
   }
 
   /**
-   * Test for adding feature links
+   * Test for adding sequence id, dbref and feature links
    */
   @Test(groups = { "Functional" })
-  public void testAddFeatureLinks()
+  public void testConstructor_links()
   {
-    // sequences from the alignment
     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
-
-    // create list of links and list of DBRefs
-    List<String> links = new ArrayList<>();
-    List<DBRefEntry> refs = new ArrayList<>();
-
-    // links as might be added into Preferences | Connections dialog
-    links.add("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$"
-            + SEQUENCE_ID + "$");
-    links.add("UNIPROT | http://www.uniprot.org/uniprot/$" + DB_ACCESSION
-            + "$");
-    links.add("INTERPRO | http://www.ebi.ac.uk/interpro/entry/$"
-            + DB_ACCESSION + "$");
-    // Gene3D entry tests for case (in)sensitivity
-    links.add("Gene3D | http://gene3d.biochem.ucl.ac.uk/Gene3D/search?sterm=$"
-            + DB_ACCESSION + "$&mode=protein");
-
-    // make seq0 dbrefs
-    refs.add(new DBRefEntry(DBRefSource.UNIPROT, "1", "P83527"));
-    refs.add(new DBRefEntry("INTERPRO", "1", "IPR001041"));
-    refs.add(new DBRefEntry("INTERPRO", "1", "IPR006058"));
-    refs.add(new DBRefEntry("INTERPRO", "1", "IPR012675"));
-    
-    // make seq1 dbrefs
-    refs.add(new DBRefEntry(DBRefSource.UNIPROT, "1", "Q9ZTS2"));
-    refs.add(new DBRefEntry("GENE3D", "1", "3.10.20.30"));
-
-    // add all the dbrefs to the sequences: Uniprot 1 each, Interpro all 3 to
-    // seq0, Gene3D to seq1
-    SequenceI seq = seqs.get(0);
-    seq.addDBRef(refs.get(0));
-
-    seq.addDBRef(refs.get(1));
-    seq.addDBRef(refs.get(2));
-    seq.addDBRef(refs.get(3));
+    final SequenceI seq0 = seqs.get(0);
+    final SequenceI seq1 = seqs.get(1);
+    final List<SequenceFeature> noFeatures = Collections
+            .<SequenceFeature> emptyList();
+    final String linkText = MessageManager.getString("action.link");
+
+    seq0.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "P83527"));
+    seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR001041"));
+    seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR012675"));
+    seq0.addDBRef(new DBRefEntry("INTERPRO", "1", "IPR006058"));
+    seq1.addDBRef(new DBRefEntry(DBRefSource.UNIPROT, "1", "Q9ZTS2"));
+    seq1.addDBRef(new DBRefEntry("GENE3D", "1", "3.10.20.30"));
     
-    seqs.get(1).addDBRef(refs.get(4));
-    seqs.get(1).addDBRef(refs.get(5));
-    
-    // get the Popup Menu for first sequence
-    List<SequenceFeature> noFeatures = Collections.<SequenceFeature> emptyList();
-    testee = new PopupMenu(parentPanel, seq, noFeatures);
+    /*
+     * check the Popup Menu for the first sequence
+     */
+    testee = new PopupMenu(parentPanel, seq0, noFeatures);
     Component[] seqItems = testee.sequenceMenu.getMenuComponents();
     JMenu linkMenu = (JMenu) seqItems[6];
+    assertEquals(linkText, linkMenu.getText());
     Component[] linkItems = linkMenu.getMenuComponents();
     
-    // check the number of links are the expected number
+    /*
+     * menu items are ordered: SEQUENCE_ID search first, then dbrefs in order
+     * of database name (and within that by order of dbref addition)
+     */
     assertEquals(5, linkItems.length);
-
-    // first entry is EMBL-EBI which just uses sequence id not accession id?
     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
+    assertEquals("INTERPRO|IPR001041",
+            ((JMenuItem) linkItems[1]).getText());
+    assertEquals("INTERPRO|IPR012675",
+            ((JMenuItem) linkItems[2]).getText());
+    assertEquals("INTERPRO|IPR006058",
+            ((JMenuItem) linkItems[3]).getText());
+    assertEquals("UNIPROT|P83527", ((JMenuItem) linkItems[4]).getText());
 
-    // sequence id for each link should match corresponding DB accession id
-    for (int i = 1; i < 4; i++)
-    {
-      String msg = seq.getName() + " link[" + i + "]";
-      assertEquals(msg, refs.get(i - 1).getSource(),
-              ((JMenuItem) linkItems[i])
-              .getText().split("\\|")[0]);
-      assertEquals(msg, refs.get(i - 1).getAccessionId(),
-              ((JMenuItem) linkItems[i])
-              .getText().split("\\|")[1]);
-    }
-
-    // get the Popup Menu for second sequence
-    seq = seqs.get(1);
-    testee = new PopupMenu(parentPanel, seq, noFeatures);
+    /*
+     * check the Popup Menu for second sequence
+     * note dbref GENE3D is matched to link Gene3D, the latter is displayed
+     */
+    testee = new PopupMenu(parentPanel, seq1, noFeatures);
     seqItems = testee.sequenceMenu.getMenuComponents();
     linkMenu = (JMenu) seqItems[6];
+    assertEquals(linkText, linkMenu.getText());
     linkItems = linkMenu.getMenuComponents();
-    
-    // check the number of links are the expected number
     assertEquals(3, linkItems.length);
-
-    // first entry is EMBL-EBI which just uses sequence id not accession id?
     assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
+    assertEquals("Gene3D|3.10.20.30", ((JMenuItem) linkItems[1]).getText());
+    assertEquals("UNIPROT|Q9ZTS2", ((JMenuItem) linkItems[2]).getText());
 
-    // sequence id for each link should match corresponding DB accession id
-    for (int i = 1; i < 3; i++)
-    {
-      String msg = seq.getName() + " link[" + i + "]";
-      assertEquals(msg, refs.get(i + 3).getSource(),
-              ((JMenuItem) linkItems[i])
-              .getText().split("\\|")[0].toUpperCase());
-      assertEquals(msg, refs.get(i + 3).getAccessionId(),
-              ((JMenuItem) linkItems[i]).getText().split("\\|")[1]);
-    }
-
-    // if there are no valid links the Links submenu is disabled
-    List<String> nomatchlinks = new ArrayList<>();
-    nomatchlinks.add("NOMATCH | http://www.uniprot.org/uniprot/$"
-            + DB_ACCESSION + "$");
+    /*
+     * if there are no valid links the Links submenu is still shown, but
+     * reduced to the EMBL-EBI lookup only (inserted by 
+     * CustomUrlProvider.choosePrimaryUrl())
+     */
+    String unmatched = "NOMATCH|http://www.uniprot.org/uniprot/$"
+            + DB_ACCESSION + "$";
+    UrlProviderFactoryI factory = new DesktopUrlProviderFactory(null,
+            unmatched, "");
+    Preferences.sequenceUrlLinks = factory.createUrlProvider();
 
-    testee = new PopupMenu(parentPanel, seq, noFeatures);
+    testee = new PopupMenu(parentPanel, seq1, noFeatures);
     seqItems = testee.sequenceMenu.getMenuComponents();
     linkMenu = (JMenu) seqItems[6];
-    assertFalse(linkMenu.isEnabled());
-
+    assertEquals(linkText, linkMenu.getText());
+    linkItems = linkMenu.getMenuComponents();
+    assertEquals(1, linkItems.length);
+    assertEquals("EMBL-EBI Search", ((JMenuItem) linkItems[0]).getText());
   }
 
-  /**
-   * Test for adding feature links
-   */
   @Test(groups = { "Functional" })
   public void testHideInsertions()
   {
index 4092cf2..81fe3d2 100644 (file)
@@ -33,6 +33,7 @@ import jalview.datamodel.Sequence;
 import jalview.gui.JvOptionPane;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -271,7 +272,7 @@ public class UrlLinkTest
     UrlLink ul = new UrlLink(DB + SEP + URL_PREFIX + DELIM + SEQUENCE_ID
             + DELIM + URL_SUFFIX);
 
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(null, linkset);
 
     String key = DB + SEP + URL_PREFIX;
@@ -291,7 +292,7 @@ public class UrlLinkTest
   {
     UrlLink ul = new UrlLink(DB + SEP + URL_PREFIX + URL_SUFFIX);
 
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(null, linkset);
 
     String key = DB + SEP + URL_PREFIX + URL_SUFFIX;
@@ -311,8 +312,8 @@ public class UrlLinkTest
   {
 
     // create list of links and list of DBRefs
-    List<String> links = new ArrayList<String>();
-    List<DBRefEntry> refs = new ArrayList<DBRefEntry>();
+    List<String> links = new ArrayList<>();
+    List<DBRefEntry> refs = new ArrayList<>();
 
     // links as might be added into Preferences | Connections dialog
     links.add("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$"
@@ -341,7 +342,7 @@ public class UrlLinkTest
     UrlLink ul = new UrlLink(DB + SEP + URL_PREFIX + DELIM + SEQUENCE_ID
             + DELIM + URL_SUFFIX);
 
-    Map<String, List<String>> linkset = new LinkedHashMap<String, List<String>>();
+    Map<String, List<String>> linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
 
     String key = seq0.getName() + SEP + URL_PREFIX + seq0.getName()
@@ -356,7 +357,7 @@ public class UrlLinkTest
 
     // Test where link takes a db annotation id and only has one dbref
     ul = new UrlLink(links.get(1));
-    linkset = new LinkedHashMap<String, List<String>>();
+    linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
 
     key = "P83527|http://www.uniprot.org/uniprot/P83527";
@@ -371,7 +372,7 @@ public class UrlLinkTest
 
     // Test where link takes a db annotation id and has multiple dbrefs
     ul = new UrlLink(links.get(2));
-    linkset = new LinkedHashMap<String, List<String>>();
+    linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
     assertEquals(3, linkset.size());
 
@@ -403,7 +404,7 @@ public class UrlLinkTest
     // Test where there are no matching dbrefs for the link
     ul = new UrlLink(DB + SEP + URL_PREFIX + DELIM + DB_ACCESSION + DELIM
             + URL_SUFFIX);
-    linkset = new LinkedHashMap<String, List<String>>();
+    linkset = new LinkedHashMap<>();
     ul.createLinksFromSeq(seq0, linkset);
     assertTrue(linkset.isEmpty());
   }
@@ -439,4 +440,27 @@ public class UrlLinkTest
 
   }
 
+  @Test(groups = { "Functional" })
+  public void testLinkComparator()
+  {
+    Comparator<String> c = UrlLink.LINK_COMPARATOR;
+    assertEquals(0, c.compare(null, null));
+    assertEquals(0, c.compare(null, "x"));
+    assertEquals(0, c.compare("y", null));
+
+    /*
+     * SEQUENCE_ID templates should come before DB_ACCESSION templates
+     * (note we are 'pretending' EMBL-EBI is a DB link so the test shows that
+     * these are not ordered by link name)
+     */
+    String seqIdUrl = "Ensembl|https://www.ensembl.org/Multi/Search/Results?q=$SEQUENCE_ID$";
+    String dbRefUrl = "EBI|https://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$DB_ACCESSION$";
+    assertTrue(c.compare(seqIdUrl, dbRefUrl) < 0);
+    assertTrue(c.compare(dbRefUrl, seqIdUrl) > 0);
+
+    String interpro = "Interpro|https://www.ebi.ac.uk/interpro/entry/$DB_ACCESSION$";
+    String prosite = "ProSite|https://prosite.expasy.org/PS00197";
+    assertTrue(c.compare(interpro, prosite) < 0);
+    assertTrue(c.compare(prosite, interpro) > 0);
+  }
 }