Merge branch 'develop' into features/JAL-2316 features/JAL-2316
authorJim Procter <jprocter@issues.jalview.org>
Thu, 9 Feb 2017 11:50:07 +0000 (11:50 +0000)
committerJim Procter <jprocter@issues.jalview.org>
Thu, 9 Feb 2017 11:50:07 +0000 (11:50 +0000)
40 files changed:
examples/exampleFeatures.txt
help/help.jhm
help/helpTOC.xml
help/html/features/preferences.html
help/html/webServices/urllinks.html
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/IdPanel.java
src/jalview/bin/Cache.java
src/jalview/gui/DasSourceBrowser.java
src/jalview/gui/Desktop.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Preferences.java
src/jalview/gui/WsPreferences.java
src/jalview/jbgui/GPreferences.java
src/jalview/jbgui/GSequenceLink.java
src/jalview/urls/CustomUrlProvider.java [new file with mode: 0644]
src/jalview/urls/IdOrgSettings.java [new file with mode: 0644]
src/jalview/urls/IdentifiersUrlProvider.java [new file with mode: 0644]
src/jalview/urls/UrlLinkDisplay.java [new file with mode: 0644]
src/jalview/urls/UrlLinkTableModel.java [new file with mode: 0644]
src/jalview/urls/UrlProvider.java [new file with mode: 0644]
src/jalview/urls/UrlProviderImpl.java [new file with mode: 0644]
src/jalview/urls/api/UrlProviderFactoryI.java [new file with mode: 0644]
src/jalview/urls/api/UrlProviderI.java [new file with mode: 0644]
src/jalview/urls/applet/AppletUrlProviderFactory.java [new file with mode: 0644]
src/jalview/urls/desktop/DesktopUrlProviderFactory.java [new file with mode: 0644]
src/jalview/util/UrlConstants.java
src/jalview/util/UrlLink.java
src/jalview/ws/utils/UrlDownloadClient.java [new file with mode: 0644]
test/jalview/urls/AppletUrlProviderFactoryTest.java [new file with mode: 0644]
test/jalview/urls/CustomUrlProviderTest.java [new file with mode: 0644]
test/jalview/urls/DesktopUrlProviderFactoryTest.java [new file with mode: 0644]
test/jalview/urls/IdentifiersUrlProviderTest.java [new file with mode: 0644]
test/jalview/urls/UrlLinkDisplayTest.java [new file with mode: 0644]
test/jalview/urls/UrlLinkTableModelTest.java [new file with mode: 0644]
test/jalview/urls/UrlProviderTest.java [new file with mode: 0644]
test/jalview/util/UrlLinkTest.java
test/jalview/ws/utils/UrlDownloadClientTest.java [new file with mode: 0644]

index 2de9817..83dc4b1 100755 (executable)
@@ -1,5 +1,5 @@
 ST-TURN-IIL    blue|255,0,255|absolute|20.0|95.0|below|66.0
-GAMMA-TURN-CLASSIC     red|0,255,255|20.0|95.0|below|66.0
+GAMMA-TURN-CLASSIC     lightGray|0,255,255|20.0|95.0|below|66.0
 BETA-TURN-IR   9a6a94
 BETA-TURN-IL   d6a6ca
 BETA-BULGE     1dc451
index f69ed00..984c2d1 100755 (executable)
    
    <mapID target="uniprotfetcher" url="html/features/uniprotsequencefetcher.html" />
    
+   <mapID target="urllinks" url="html/webServices/urllinks.html" />
+   <mapID target="linksprefs" url="html/features/Preferences.html#links" />
+
    <mapID target="backIcon" url="icons/back.png" />
    <mapID target="forwardIcon" url="icons/forward.png" />
    <mapID target="homeIcon" url="icons/Home.png" />
index 54abd53..482ccdf 100755 (executable)
                        <tocitem text="Chimera Viewer" target="chimera" />                      
                </tocitem>
                <tocitem text="Viewing RNA structures" target="varna" expand="false"/>
+               <tocitem text="Opening URLs from Jalview" target="urllinks" expand="true">
+                   <tocitem text="Configuring URL Links" target="urllinkspref" />
+               </tocitem>
                <tocitem text="VAMSAS Data Exchange" target="vamsas">
                        <!-- what can Jalview share with other apps -->
                        <!-- what other apps exist -->
index 6a8c86c..da045ba 100755 (executable)
       and displaying structure information.
     </li>
     <li>The <a href="#connections"><strong>&quot;Connections&quot;</strong>
-        Preferences</a> tab allows you to change the links made from Jalview
-      to your default web browser.
+        Preferences</a> tab allows you to configure Jalview's internet
+      settings and specify your default web browser.
+    </li>
+    <li>The <a href="#links"><strong>&quot;Links&quot;</strong>
+        Preferences</a> tab shows the currently configured <em>URL
+        Links</em> shown in the <strong>Link</strong> submenu in the Sequence
+      ID popup menu.
     </li>
     <li>The <a href="#output"><strong>&quot;Output&quot;</strong>
         Preferences</a> tab contains settings affecting the export of
         Preferences tab</strong></a>
   </p>
   <p>
-    <em>URL Link From Sequence ID</em><br> These definitions are
-    used to generate URLs from a sequence's ID or database cross
-    references. Read more about <a
-      href="../webServices/urllinks.html#urllinks">configuring
-      URL links here</a>.
-  </p>
-  <p>
     <em>Default Browser (Unix)</em><br> Its difficult in Java to
     detect the default web browser for Unix users. If Jalview can't find
     your default web browser, enter the name or full path to your web
       statement</a> for more information.
   </p>
   <p>
+    <a name="links"><strong>The &quot;Links&quot; Preferences
+        tab</strong></a>
+  </p>
+  <p>
+    This panel shows a table, and two sections - <em>Edit</em> and <em>Filter</em>.
+    The table shows the available URL link definitions (consisting of a
+    database, Name, and URL template string), a checkbox <em>In
+      Menu</em> which indicates if the link is enabled, and <em>Double
+      Click</em> which marks the link that will be opened if a sequence's ID
+    is double clicked. The table can be sorted by clicking on the column headers.
+  </p>
+  <p><em>Edit Links</em><br /> This section contains three buttons,
+    <em>New</em>, <em>Edit</em> and <em>Delete</em>, which allow you to
+    create, modify and remove user-defined URL links from the Sequence
+    ID's links submenu.
+  </p>
+  <p>
+    <em>Filter</em><br /> The <em>Filter text</em> box allows you to
+    quickly show rows in the table containing a particular text string.
+    The <em>Custom only</em> button limits the entries in the table to
+    just those you have configured yourself <em>via</em> the <em>Edit
+      Links</em> buttons. Press <em>Show all</em> to clear any filters.
+  <p>
+    <a href="../webServices/urllinks.html#urllinks">Read more about configuring
+      URL links.</a>
+  </p>
+  <p>
     <a name="output"><strong>Output Preferences tab</strong></a>
   </p>
   <p>
     and PDB file association (if available). The Jalview id/start-end
     option is ignored if Modeller output is selected.
   <p>
-    <a name="editing"><strong>Editing Preferences tab</strong></a>
+    <a name="editing"><strong>e&quot;Editinge&quot; Preferences tab</strong></a>
   </p>
   <p>There are currently three options available which can be
     selected / deselected.</p>
index 088a539..da5d7dd 100644 (file)
 </head>
 <body>
   <p>
-  <p>
     <strong>Opening URLs from Jalview</strong><br> Both the applet
     and the desktop application are able to open URLs as 'popups' in
-    your web browser. <br> Double-clicking on the ID of a sequence
-    will open the first URL that can be generated from its sequence ID.
+    your web browser.</p>
+    <p> Double-clicking on the ID of a sequence
+    will open whichever URL is selected for 'popups' in the <strong>&quot;Links&quot;</strong> tab of the <a
+    href="../features/preferences.html#links">Jalview desktop
+    preferences</a>.
     This is by default the EMBL-EBI site, but you can easily configure your own <a
       href="#urllinks">sequence URL links</a>.
   </p>
   <p>
-    Other links for a sequence either derived from any other configured
+    Other links for a sequence, either derived from any other configured
     URL links, or imported from the sequence's annotation, are accessed
     by right clicking to open the sequence pop-up menu, and selecting
     from the <em>Links</em> submenu.
   </p>
   <p>
     <strong><a name="urllinks">Configuring URL Links</a></strong> <br>URL
-    links are defined in the &quot;Connections&quot; tab of the <a
-    href="../features/preferences.html">Jalview desktop
+    links are defined in the &quot;Links&quot; tab of the <a
+    href="../features/preferences.html#links">Jalview desktop
     preferences</a>, or specified as <a
     href="http://www.jalview.org/examples/appletParameters.html#parameters">applet
-    parameters</a>. <br> By default the item &quot;EMBL-EBI Search&quot; is added
-    to this link menu. This link will show a web page in your default
-    browser with the selected sequence id as part of the URL.<br>
-    In the preferences dialog box, click <strong>new</strong> to add a
-    new link, and <strong>edit</strong> to modify an existing link, or <strong>delete</strong>
-    to remove it.<br> You can name the link, this will be displayed
-    on a new menu item under the &quot;Link&quot; menu when you right
-    click on a sequence id. <br> The URL string must contain a
-    token that can be replaced with a sequence ID or DB accession ID. The simplest token is
-    &quot;$SEQUENCE_ID$&quot;, which will be replaced by the chosen
-    sequence id when you click on it. 
+    parameters</a>.</p>
+  <p>
+    <em>Default Link Settings</em><br /> The &quot;EMBL-EBI Search&quot;
+    link is the default link shown in the &quot;Link&quot; submenu, and
+    opened when double-clicking on a sequence ID. When clicked, this
+    link will show a web page in your default browser with the selected
+    sequence ID as part of the URL.
+  </p>
+  <p>
+    <em>Adding additional links</em><br /> You can configure your own
+    links via the Jalview <a href="../features/preferences.html#links"><strong>Preferences</strong></a>
+    dialog. Jalview also provides persistent URLs for many common
+    bioinformatics databases. These links are downloaded by Jalview from
+    the <em>identifiers.org</em> website, and the names and URLs are not
+    user editable.
   </p>
   <p>
-    eg.<br> UniRef100 =
-    http://www.ebi.uniprot.org/uniprot-srv/uniRefView.do?proteinAc=$SEQUENCE_ID$&amp;library=uniref100<br>
-    Swissprot = http://www.expasy.org/uniprot/$SEQUENCE_ID$ <br> <br>
+    <em>Creating your own URL link</em> URL links are specified as a
+    template containing special tokens that Jalview will replace with
+    the Sequence ID or Database Accession of the sequence when you
+    double click on its ID or open it's <strong>Link</strong> submenu.
+    Link URL templates must contain at least one token. 
+  </p>
+    <em>eg.</em><pre> UniRef100 =
+    http://www.ebi.uniprot.org/uniprot-srv/uniRefView.do?proteinAc=$SEQUENCE_ID$&amp;library=uniref100<br/>
+    Swissprot = http://www.expasy.org/uniprot/$SEQUENCE_ID$ <br> </pre>
+  <p>
     Links will also be made for any database cross references associated
     with the sequence where the database name exactly matches a URL link
     name. In this case, the $DB_ACCESSION$ string will be replaced with
   <p>
     <strong>Regular Expression Substitution</strong><br> A url may
     contain a string of the form $SEQUENCE_ID=/<em>regular
-    expression</em>/=$ or $DB_ACCESSION=/<em>regular expression</em>/=$. 
-    In this case, the regular expression will be
-    applied to the full sequence ID or DB accession ID string and the resulting match will
+      expression</em>/=$ or $DB_ACCESSION=/<em>regular expression</em>/=$. In
+    this case, the regular expression will be applied to the full
+    sequence ID or DB accession ID string and the resulting match will
     be inserted into the URL. Groups of parentheses can be used to
     specify which regions of the regular expression will be used to
     generate the URL:
+  
   <ul>
     <li>Each top level parenthesis will yield a URL containing the
       text matched within that parenthesis.</li>
     <li>Regions matching sub-parentheses within a top-level
       parenthesis will be concatenated to form the text inserted into
       the URL for the top-level parenthesis.</li>
-    <em>Please Note:
-      <ul>
-        <li>The regular expressions supported by Jalview are those
-          provided by the <a href="http://www.javaregex.com">Stevesoft
-            javaregex package</a>.
-        </li>
-        <li>Some characters must be escaped when specifying them as
-          a match within a regular expression.</li>
-      </ul> <br> Many Thanks to Bernd Brandt of the Free University of
-      Amsterdam for testing this new regular-expression expansion
-      feature!
-    </em>
-    <em>
   </ul>
-  </p>
-  </p>
+  <em>Please Note:</em>
+    <ul>
+      <li>The regular expressions supported by Jalview are those
+        provided by the <a href="http://www.javaregex.com">Stevesoft
+          javaregex package</a>.
+      </li>
+      <li>Some characters must be escaped when specifying them as a
+        match within a regular expression.</li>
+    </ul> <br> Many Thanks to Bernd Brandt of the Free University of
+    Amsterdam for testing the regular-expression expansion feature!
 </body>
 </html>
index 1ba0c79..f720f39 100644 (file)
@@ -138,7 +138,8 @@ action.view_flanking_regions = Show flanking regions
 label.view_flanking_regions = Show sequence data either side of the subsequences involved in this alignment
 label.structures_manager = Structures Manager
 label.nickname = Nickname:
-label.url = URL:
+label.url = URL
+label.url\: = URL:
 label.input_file_url = Enter URL or Input File
 label.select_feature = Select feature
 label.name = Name
@@ -413,7 +414,6 @@ label.couldnt_import_as_vamsas_session = Couldn't import {0} as a new vamsas ses
 label.vamsas_document_import_failed = Vamsas Document Import Failed
 label.couldnt_locate = Couldn't locate {0}
 label.url_not_found = URL not found
-label.no_link_selected = No link selected
 label.new_sequence_url_link = New sequence URL link
 label.cannot_edit_annotations_in_wrapped_view = Cannot edit annotations in wrapped view
 label.wrapped_view_no_edit = Wrapped view - no edit
@@ -1271,4 +1271,21 @@ label.SEQUENCE_ID_no_longer_used = $SEQUENCE_ID$ is no longer used for DB access
 label.SEQUENCE_ID_for_DB_ACCESSION1 = Please review your URL links in the 'Connections' tab of the Preferences window:
 label.SEQUENCE_ID_for_DB_ACCESSION2 = URL links using '$SEQUENCE_ID$' for DB accessions now use '$DB_ACCESSION$'.
 label.do_not_display_again = Do not display this message again
+exception.url_cannot_have_miriam_id = {0} is a MIRIAM id and cannot be used as a custom url name
+exception.url_cannot_have_duplicate_id = {0} cannot be used as a label for more than one line
+label.filter = Filter text:
+action.customfilter = Custom only
+action.showall = Show All
+label.insert = Insert:
+action.seq_id = $SEQUENCE_ID$
+action.db_acc = $DB_ACCESSION$
+label.primary = Double Click
+label.inmenu = In Menu
+label.id = ID
+label.database = Database
+label.urltooltip = Only one url, which must use a sequence id, can be selected for the 'On Click' option
+label.edit_sequence_url_link = Edit sequence URL link
+warn.name_cannot_be_duplicate = User-defined URL names must be unique and cannot be MIRIAM ids
+label.invalid_name = Invalid Name !
 label.output_seq_details = Output Sequence Details to list all database references
+label.urllinks = Links
\ No newline at end of file
index 7f769b3..d408fee 100644 (file)
@@ -135,7 +135,8 @@ action.view_flanking_regions = Mostrar flancos
 label.view_flanking_regions = Mostrar los datos de la secuencia a ambos lados de las subsecuencias implicadas en este alineamiento
 label.structures_manager = Administrar estructuras
 label.nickname = Sobrenombre:
-label.url = URL: 
+label.url\: = URL:
+label.url = URL 
 label.input_file_url = Introducir URL en el fichero de entrada
 label.select_feature = Seleccionar característica
 label.name = Nombre
@@ -380,7 +381,6 @@ label.couldnt_import_as_vamsas_session = No se pudo importar {0} como una nueva
 label.vamsas_document_import_failed =  Fallo en la importación del documento Vamsas
 label.couldnt_locate = No se pudo localizar {0}
 label.url_not_found = URL no encontrada
-label.no_link_selected = Enlace no seleccionado
 label.new_sequence_url_link = Enlace a una nueva secuencia URL
 label.cannot_edit_annotations_in_wrapped_view = No se pueden editar anotaciones en vista envolvente
 label.wrapped_view_no_edit = Vista envolvente - no editar
@@ -1271,4 +1271,21 @@ label.SEQUENCE_ID_no_longer_used = $SEQUENCE_ID$ no se utiliza m
 label.SEQUENCE_ID_for_DB_ACCESSION1 = Por favor, revise sus URLs en la pestaña 'Conexiones' de la ventana de Preferencias:
 label.SEQUENCE_ID_for_DB_ACCESSION2 = URL enlaza usando '$SEQUENCE_ID$' para accesiones DB ahora usar '$DB_ACCESSION$'.
 label.do_not_display_again = No mostrar este mensaje de nuevo
+exception.url_cannot_have_miriam_id = {0} es una id MIRIAM y no puede ser usada como nombre url personalizado
+exception.url_cannot_have_duplicate_id = {0} no puede ser usada como etiqueta en más de un enlace
+label.filter = Filtrar texto:
+action.customfilter = Sólo personalizado
+action.showall = Mostrar todo
+label.insert = Insertar:
+action.seq_id = $SEQUENCE_ID$
+action.db_acc = $DB_ACCESSION$
+label.primary = Doble clic
+label.inmenu = En Menú
+label.id = ID
+label.database = Base de datos
+label.urltooltip = Sólo una url, que debe usar una id de secuencia, puede ser seleccionada en la opción 'On Click'
+label.edit_sequence_url_link = Editar link de secuencia URL
+warn.name_cannot_be_duplicate = Los nombres URL definidos por el usuario deben ser Ãºnicos y no pueden ser ids de MIRIAM
+label.invalid_name = Nombre inválido !
 label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas
+label.urllinks = Enlaces
\ No newline at end of file
index 055fcf3..8fd317a 100644 (file)
@@ -210,7 +210,7 @@ public class APopupMenu extends java.awt.PopupMenu implements
   Menu menu1 = new Menu();
 
   public APopupMenu(AlignmentPanel apanel, final SequenceI seq,
-          Vector<String> links)
+          List<String> links)
   {
     // /////////////////////////////////////////////////////////
     // If this is activated from the sequence panel, the user may want to
index 182f20e..b03a638 100755 (executable)
  */
 package jalview.appletgui;
 
-import static jalview.util.UrlConstants.EMBLEBI_STRING;
-import static jalview.util.UrlConstants.SRS_STRING;
-
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
-import jalview.util.UrlLink;
+import jalview.urls.api.UrlProviderFactoryI;
+import jalview.urls.api.UrlProviderI;
+import jalview.urls.applet.AppletUrlProviderFactory;
 import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.BorderLayout;
@@ -36,8 +35,8 @@ import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Vector;
 
 public class IdPanel extends Panel implements MouseListener,
         MouseMotionListener
@@ -55,7 +54,7 @@ public class IdPanel extends Panel implements MouseListener,
 
   boolean mouseDragging = false;
 
-  java.util.Vector links = new java.util.Vector();
+  UrlProviderI urlProvider = null;
 
   public IdPanel(AlignViewport av, AlignmentPanel parent)
   {
@@ -69,6 +68,9 @@ public class IdPanel extends Panel implements MouseListener,
 
     String label, url;
     // TODO: add in group link parameter
+
+    // make a list of label,url pairs
+    HashMap<String, String> urlList = new HashMap<String, String>();
     if (av.applet != null)
     {
       for (int i = 1; i < 10; i++)
@@ -76,26 +78,22 @@ public class IdPanel extends Panel implements MouseListener,
         label = av.applet.getParameter("linkLabel_" + i);
         url = av.applet.getParameter("linkURL_" + i);
 
-        if (label != null && url != null)
+        // only add non-null parameters
+        if (label != null)
         {
-          links.addElement(label + "|" + url);
+          urlList.put(label, url);
         }
-
       }
-    }
-    {
-      // upgrade old SRS link
-      int srsPos = links.indexOf(SRS_STRING);
-      if (srsPos > -1)
+
+      if (!urlList.isEmpty())
       {
-        links.setElementAt(EMBLEBI_STRING, srsPos);
+        // set default as first entry in list
+        String defaultUrl = av.applet.getParameter("linkLabel_1");
+        UrlProviderFactoryI factory = new AppletUrlProviderFactory(
+                defaultUrl, urlList);
+        urlProvider = factory.createUrlProvider();
       }
     }
-    if (links.size() < 1)
-    {
-      links = new java.util.Vector();
-      links.addElement(EMBLEBI_STRING);
-    }
   }
 
   Tooltip tooltip;
@@ -217,7 +215,7 @@ public class IdPanel extends Panel implements MouseListener,
       return;
     }
 
-    // DEFAULT LINK IS FIRST IN THE LINK LIST
+    // get the sequence details
     int seq = alignPanel.seqPanel.findSeq(e);
     SequenceI sq = av.getAlignment().getSequenceAt(seq);
     if (sq == null)
@@ -226,53 +224,11 @@ public class IdPanel extends Panel implements MouseListener,
     }
     String id = sq.getName();
 
-    String target = null;
-    String url = null;
-    int i = 0;
-    while (url == null && i < links.size())
-    {
-      // DEFAULT LINK IS FIRST IN THE LINK LIST
-      // BUT IF ITS A REGEX AND DOES NOT MATCH THE NEXT ONE WILL BE TRIED
-      url = links.elementAt(i++).toString();
-      jalview.util.UrlLink urlLink = null;
-      try
-      {
-        urlLink = new UrlLink(url);
-        target = urlLink.getTarget();
-      } catch (Exception foo)
-      {
-        System.err.println("Exception for URLLink '" + url + "'");
-        foo.printStackTrace();
-        url = null;
-        continue;
-      }
-
-      if (urlLink.usesDBAccession())
-      {
-        // this URL requires an accession id, not the name of a sequence
-        url = null;
-        continue;
-      }
-
-      if (!urlLink.isValid())
-      {
-        System.err.println(urlLink.getInvalidMessage());
-        url = null;
-        continue;
-      }
-
-      String urls[] = urlLink.makeUrls(id, true);
-      if (urls == null || urls[0] == null || urls[0].length() < 1)
-      {
-        url = null;
-        continue;
-      }
-      // just take first URL made from regex
-      url = urls[1];
-    }
+    // get the default url with the sequence details filled in
+    String url = urlProvider.getPrimaryUrl(id);
+    String target = urlProvider.getPrimaryTarget(id);
     try
     {
-
       alignPanel.alignFrame.showURL(url, target);
     } catch (Exception ex)
     {
@@ -331,11 +287,8 @@ public class IdPanel extends Panel implements MouseListener,
 
       // build a new links menu based on the current links + any non-positional
       // features
-      Vector nlinks = new Vector();
-      for (int l = 0, lSize = links.size(); l < lSize; l++)
-      {
-        nlinks.addElement(links.elementAt(l));
-      }
+      List<String> nlinks = urlProvider.getLinksForMenu();
+
       SequenceFeature sf[] = sq == null ? null : sq.getSequenceFeatures();
       for (int sl = 0; sf != null && sl < sf.length; sl++)
       {
@@ -345,7 +298,7 @@ public class IdPanel extends Panel implements MouseListener,
           {
             for (int l = 0, lSize = sf[sl].links.size(); l < lSize; l++)
             {
-              nlinks.addElement(sf[sl].links.elementAt(l));
+              nlinks.add(sf[sl].links.elementAt(l));
             }
           }
         }
index 9363c23..48c1ee9 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.gui.UserDefinedColours;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureImportSettings;
+import jalview.urls.IdOrgSettings;
 import jalview.util.ColorUtils;
 import jalview.ws.dbsources.das.api.DasSourceRegistryI;
 import jalview.ws.dbsources.das.datamodel.DasSourceRegistry;
@@ -127,6 +128,10 @@ import org.apache.log4j.SimpleLayout;
  * <li>SORT_ALIGNMENT (No sort|Id|Pairwise Identity)</li>
  * <li>SEQUENCE_LINKS list of name|URL pairs for opening a url with
  * $SEQUENCE_ID$</li>
+ * <li>STORED_LINKS list of name|url pairs which user has entered but are not
+ * currently used
+ * <li>DEFAULT_LINK name of single url to be used when user double clicks a
+ * sequence id (must be in SEQUENCE_LINKS or STORED_LINKS)
  * <li>GROUP_LINKS list of name|URL[|&lt;separator&gt;] tuples - see
  * jalview.utils.GroupURLLink for more info</li>
  * <li>DAS_REGISTRY_URL the registry to query</li>
@@ -184,6 +189,8 @@ import org.apache.log4j.SimpleLayout;
  * <li>STRUCTURE_DISPLAY choose from JMOL (default) or CHIMERA for 3D structure
  * display</li>
  * <li>CHIMERA_PATH specify full path to Chimera program (if non-standard)</li>
+ * <li>ID_ORG_HOSTURL location of jalview service providing identifiers.org urls
+ * </li>
  * 
  * </ul>
  * Deprecated settings:
@@ -225,6 +232,9 @@ public class Cache
 
   public static final String DAS_ACTIVE_SOURCE = "DAS_ACTIVE_SOURCE";
 
+  /**
+   * Sifts settings
+   */
   public static final String DEFAULT_SIFTS_DOWNLOAD_DIR = System
           .getProperty("user.home")
           + File.separatorChar
@@ -235,6 +245,12 @@ public class Cache
   private final static String DEFAULT_FAIL_SAFE_PID_THRESHOLD = "30";
 
   /**
+   * Identifiers.org download settings
+   */
+  private static final String ID_ORG_FILE = System.getProperty("user.home")
+          + File.separatorChar + ".identifiers.org.ids.json";
+
+  /**
    * Allowed values are PDB or mmCIF
    */
   private final static String PDB_DOWNLOAD_FORMAT = PDBEntry.Type.MMCIF
@@ -445,6 +461,10 @@ public class Cache
             "sifts_cache_threshold_in_days",
             DEFAULT_CACHE_THRESHOLD_IN_DAYS));
 
+    IdOrgSettings.setUrl(getDefault("ID_ORG_HOSTURL",
+            "http://www.jalview.org/services/identifiers"));
+    IdOrgSettings.setDownloadLocation(ID_ORG_FILE);
+
     System.out
             .println("Jalview Version: " + codeVersion + codeInstallation);
 
index 8c8f228..c5ec067 100644 (file)
@@ -453,7 +453,7 @@ public class DasSourceBrowser extends GDasSourceBrowser implements
     pane12.add(nametf, BorderLayout.EAST);
     panel.add(pane12, BorderLayout.NORTH);
     pane12 = new JPanel(new BorderLayout());
-    pane12.add(new JLabel(MessageManager.getString("label.url")),
+    pane12.add(new JLabel(MessageManager.getString("label.url:")),
             BorderLayout.NORTH);
     pane12.add(seqs, BorderLayout.SOUTH);
     pane12.add(urltf, BorderLayout.EAST);
index 0321662..dc16a57 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.gui;
 
-import static jalview.util.UrlConstants.EMBLEBI_STRING;
 import static jalview.util.UrlConstants.SEQUENCE_ID;
 
 import jalview.api.AlignViewportI;
@@ -39,11 +38,14 @@ import jalview.io.JalviewFileView;
 import jalview.jbgui.GSplitFrame;
 import jalview.jbgui.GStructureViewer;
 import jalview.structure.StructureSelectionManager;
+import jalview.urls.IdOrgSettings;
 import jalview.util.ImageMaker;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.util.UrlConstants;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.ParamManager;
+import jalview.ws.utils.UrlDownloadClient;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -78,6 +80,7 @@ import java.beans.PropertyChangeListener;
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Hashtable;
@@ -392,6 +395,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
     showNews.setVisible(false);
 
+    getIdentifiersOrgData();
+
     checkURLLinks();
 
     this.addWindowListener(new WindowAdapter()
@@ -525,6 +530,29 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     });
   }
 
+  public void getIdentifiersOrgData()
+  {
+    // Thread off the identifiers fetcher
+    addDialogThread(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        Cache.log.debug("Downloading data from identifiers.org");
+        UrlDownloadClient client = new UrlDownloadClient();
+        try
+        {
+          client.download(IdOrgSettings.getUrl(),
+                  IdOrgSettings.getDownloadLocation());
+        } catch (IOException e)
+        {
+          Cache.log.debug("Exception downloading identifiers.org data"
+                  + e.getMessage());
+        }
+      }
+    });
+  }
+
   @Override
   protected void showNews_actionPerformed(ActionEvent e)
   {
@@ -2290,7 +2318,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
         {
           // check what the actual links are - if it's just the default don't
           // bother with the warning
-          Vector<String> links = Preferences.sequenceURLLinks;
+          List<String> links = Preferences.sequenceUrlLinks
+                  .getLinksForMenu();
 
           // only need to check links if there is one with a
           // SEQUENCE_ID which is not the default EMBL_EBI link
@@ -2300,7 +2329,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
           while (li.hasNext())
           {
             String link = li.next();
-            if (link.contains(SEQUENCE_ID) && !link.equals(EMBLEBI_STRING))
+            if (link.contains(SEQUENCE_ID)
+                    && !link.equals(UrlConstants.DEFAULT_STRING))
             {
               check = true;
               int barPos = link.indexOf("|");
index 59d12d9..6ae19f0 100755 (executable)
@@ -27,7 +27,6 @@ import jalview.datamodel.SequenceI;
 import jalview.io.SequenceAnnotationReport;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
-import jalview.util.UrlLink;
 import jalview.viewmodel.AlignmentViewport;
 
 import java.awt.BorderLayout;
@@ -37,9 +36,7 @@ import java.awt.event.MouseMotionListener;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
 import java.util.List;
-import java.util.Vector;
 
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
 import javax.swing.ToolTipManager;
@@ -199,56 +196,10 @@ public class IdPanel extends JPanel implements MouseListener,
       return;
     }
 
-    Vector links = Preferences.sequenceURLLinks;
-    if (links == null || links.size() < 1)
-    {
-      return;
-    }
-
     int seq = alignPanel.getSeqPanel().findSeq(e);
-    String url = null;
-    int i = 0;
     String id = av.getAlignment().getSequenceAt(seq).getName();
-    while (url == null && i < links.size())
-    {
-      // DEFAULT LINK IS FIRST IN THE LINK LIST
-      // BUT IF ITS A REGEX AND DOES NOT MATCH THE NEXT ONE WILL BE TRIED
-      url = links.elementAt(i++).toString();
-      jalview.util.UrlLink urlLink = null;
-      try
-      {
-        urlLink = new UrlLink(url);
-      } catch (Exception foo)
-      {
-        jalview.bin.Cache.log.error("Exception for URLLink '" + url + "'",
-                foo);
-        url = null;
-        continue;
-      }
+    String url = Preferences.sequenceUrlLinks.getPrimaryUrl(id);
 
-      if (urlLink.usesDBAccession())
-      {
-        // this URL requires an accession id, not the name of a sequence
-        url = null;
-        continue;
-      }
-
-      if (!urlLink.isValid())
-      {
-        jalview.bin.Cache.log.error(urlLink.getInvalidMessage());
-        url = null;
-        continue;
-      }
-
-      String urls[] = urlLink.makeUrls(id, true);
-      if (urls == null || urls[0] == null || urls[0].length() < 4)
-      {
-        url = null;
-        continue;
-      }
-      // just take first URL made from regex
-      url = urls[1];
-    }
     try
     {
       jalview.util.BrowserLauncher.openURL(url);
@@ -375,7 +326,7 @@ public class IdPanel extends JPanel implements MouseListener,
     Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq2);
     // build a new links menu based on the current links + any non-positional
     // features
-    Vector<String> nlinks = new Vector<String>(Preferences.sequenceURLLinks);
+    List<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
     SequenceFeature sfs[] = sq == null ? null : sq.getSequenceFeatures();
     if (sfs != null)
     {
@@ -387,7 +338,7 @@ public class IdPanel extends JPanel implements MouseListener,
           {
             for (int l = 0, lSize = sf.links.size(); l < lSize; l++)
             {
-              nlinks.addElement(sf.links.elementAt(l));
+              nlinks.add(sf.links.elementAt(l));
             }
           }
         }
index d32ff46..cf80a6d 100755 (executable)
  */
 package jalview.gui;
 
-import static jalview.util.UrlConstants.DB_ACCESSION;
-import static jalview.util.UrlConstants.EMBLEBI_STRING;
-import static jalview.util.UrlConstants.SEQUENCE_ID;
-import static jalview.util.UrlConstants.SRS_STRING;
-
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.bin.Cache;
 import jalview.gui.Help.HelpId;
@@ -37,12 +32,18 @@ import jalview.jbgui.GSequenceLink;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueColourScheme;
+import jalview.urls.UrlLinkTableModel;
+import jalview.urls.api.UrlProviderFactoryI;
+import jalview.urls.api.UrlProviderI;
+import jalview.urls.desktop.DesktopUrlProviderFactory;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.util.UrlConstants;
 import jalview.ws.sifts.SiftsSettings;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.event.ActionEvent;
@@ -51,14 +52,24 @@ import java.awt.event.MouseEvent;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.StringTokenizer;
-import java.util.Vector;
 
 import javax.help.HelpSetException;
 import javax.swing.JColorChooser;
 import javax.swing.JFileChooser;
 import javax.swing.JInternalFrame;
 import javax.swing.JPanel;
+import javax.swing.ListSelectionModel;
+import javax.swing.RowFilter;
+import javax.swing.RowSorter;
+import javax.swing.SortOrder;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableModel;
+import javax.swing.table.TableRowSorter;
 
 import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
 
@@ -104,7 +115,9 @@ public class Preferences extends GPreferences
    * Holds name and link separated with | character. Sequence ID must be
    * $SEQUENCE_ID$ or $SEQUENCE_ID=/.possible | chars ./=$
    */
-  public static Vector<String> sequenceURLLinks;
+  public static UrlProviderI sequenceUrlLinks;
+
+  public static UrlLinkTableModel dataModel;
 
   /**
    * Holds name and link separated with | character. Sequence IDS and Sequences
@@ -117,40 +130,23 @@ public class Preferences extends GPreferences
   public static List<String> groupURLLinks;
   static
   {
-    String string = Cache.getDefault("SEQUENCE_LINKS", EMBLEBI_STRING);
-    sequenceURLLinks = new Vector<String>();
-
-    try
+    // get links selected to be in the menu (SEQUENCE_LINKS)
+    // and links entered by the user but not selected (STORED_LINKS)
+    String inMenuString = Cache.getDefault("SEQUENCE_LINKS", "");
+    String notInMenuString = Cache.getDefault("STORED_LINKS", "");
+    String defaultUrl = Cache.getDefault("DEFAULT_URL",
+            UrlConstants.DEFAULT_LABEL);
+
+    // if both links lists are empty, add the DEFAULT_URL link
+    // otherwise we assume the default link is in one of the lists
+    if (inMenuString.isEmpty() && notInMenuString.isEmpty())
     {
-      StringTokenizer st = new StringTokenizer(string, "|");
-      while (st.hasMoreElements())
-      {
-        String name = st.nextToken();
-        String url = st.nextToken();
-        // check for '|' within a regex
-        int rxstart = url.indexOf("$" + DB_ACCESSION + "$");
-        if (rxstart == -1)
-        {
-          rxstart = url.indexOf("$" + SEQUENCE_ID + "$");
-        }
-        while (rxstart == -1 && url.indexOf("/=$") == -1)
-        {
-          url = url + "|" + st.nextToken();
-        }
-        sequenceURLLinks.addElement(name + "|" + url);
-      }
-    } catch (Exception ex)
-    {
-      System.out.println(ex + "\nError parsing sequence links");
-    }
-    {
-      // upgrade old SRS link
-      int srsPos = sequenceURLLinks.indexOf(SRS_STRING);
-      if (srsPos > -1)
-      {
-        sequenceURLLinks.setElementAt(EMBLEBI_STRING, srsPos);
-      }
+      inMenuString = UrlConstants.DEFAULT_STRING;
     }
+    UrlProviderFactoryI factory = new DesktopUrlProviderFactory(defaultUrl,
+            inMenuString, notInMenuString);
+    sequenceUrlLinks = factory.createUrlProvider();
+    dataModel = new UrlLinkTableModel(sequenceUrlLinks);
 
     /**
      * TODO: reformulate groupURL encoding so two or more can be stored in the
@@ -160,8 +156,6 @@ public class Preferences extends GPreferences
     groupURLLinks = new ArrayList<String>();
   }
 
-  Vector<String> nameLinks, urlLinks;
-
   JInternalFrame frame;
 
   DasSourceBrowser dasSource;
@@ -349,20 +343,128 @@ public class Preferences extends GPreferences
     /*
      * Set Connections tab defaults
      */
-    nameLinks = new Vector<String>();
-    urlLinks = new Vector<String>();
-    for (int i = 0; i < sequenceURLLinks.size(); i++)
+
+    // set up sorting
+    linkUrlTable.setModel(dataModel);
+    final TableRowSorter<TableModel> sorter = new TableRowSorter<>(
+            linkUrlTable.getModel());
+    linkUrlTable.setRowSorter(sorter);
+    List<RowSorter.SortKey> sortKeys = new ArrayList<>();
+
+    UrlLinkTableModel m = (UrlLinkTableModel) linkUrlTable.getModel();
+    sortKeys.add(new RowSorter.SortKey(m.getPrimaryColumn(),
+            SortOrder.DESCENDING));
+    sortKeys.add(new RowSorter.SortKey(m.getSelectedColumn(),
+            SortOrder.DESCENDING));
+    sortKeys.add(new RowSorter.SortKey(m.getNameColumn(),
+            SortOrder.ASCENDING));
+
+    sorter.setSortKeys(sortKeys);
+    sorter.sort();
+    
+    // set up filtering
+    ActionListener onReset;
+    onReset = new ActionListener()
     {
-      String link = sequenceURLLinks.elementAt(i).toString();
-      nameLinks.addElement(link.substring(0, link.indexOf("|")));
-      urlLinks.addElement(link.substring(link.indexOf("|") + 1));
-    }
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        filterTB.setText("");
+        sorter.setRowFilter(RowFilter.regexFilter(""));
+      }
+
+    };
+    doReset.addActionListener(onReset);
+
+    // filter to display only custom urls
+    final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<TableModel, Object>()
+    {
+      @Override
+      public boolean include(
+              Entry<? extends TableModel, ? extends Object> entry)
+      {
+        return ((UrlLinkTableModel) entry.getModel()).isUserEntry(entry);
+      }
+    };
+
+    final TableRowSorter<TableModel> customSorter = new TableRowSorter<>(
+            linkUrlTable.getModel());
+    customSorter.setRowFilter(customUrlFilter);
+
+    ActionListener onCustomOnly;
+    onCustomOnly = new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        filterTB.setText("");
+        sorter.setRowFilter(customUrlFilter);
+      }
+    };
+    userOnly.addActionListener(onCustomOnly);
+
+    filterTB.getDocument().addDocumentListener(new DocumentListener()
+    {
+      String caseInsensitiveFlag = "(?i)";
 
-    updateLinkData();
+      @Override
+      public void changedUpdate(DocumentEvent e)
+      {
+        sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag
+                + filterTB.getText()));
+      }
+
+      @Override
+      public void removeUpdate(DocumentEvent e)
+      {
+        sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag
+                + filterTB.getText()));
+      }
+
+      @Override
+      public void insertUpdate(DocumentEvent e)
+      {
+        sorter.setRowFilter(RowFilter.regexFilter(caseInsensitiveFlag
+                + filterTB.getText()));
+      }
+    });
+
+    // set up list selection functionality
+    linkUrlTable.getSelectionModel().addListSelectionListener(
+            new UrlListSelectionHandler());
+
+    // set up radio buttons
+    int onClickCol = ((UrlLinkTableModel) linkUrlTable.getModel())
+            .getPrimaryColumn();
+    String onClickName = linkUrlTable.getColumnName(onClickCol);
+    linkUrlTable.getColumn(onClickName).setCellRenderer(
+               new RadioButtonRenderer());
+    linkUrlTable.getColumn(onClickName)
+            .setCellEditor(new RadioButtonEditor());
+
+    // get boolean columns and resize those to min possible
+    for (int column = 0; column < linkUrlTable.getColumnCount(); column++)
+    {
+      if (linkUrlTable.getModel().getColumnClass(column)
+              .equals(Boolean.class))
+      {
+        TableColumn tableColumn = linkUrlTable.getColumnModel().getColumn(
+                column);
+        int preferredWidth = tableColumn.getMinWidth();
+
+        TableCellRenderer cellRenderer = linkUrlTable.getCellRenderer(0,
+                column);
+        Component c = linkUrlTable.prepareRenderer(cellRenderer, 0, column);
+        int cwidth = c.getPreferredSize().width
+                + linkUrlTable.getIntercellSpacing().width;
+        preferredWidth = Math.max(preferredWidth, cwidth);
+
+        tableColumn.setPreferredWidth(preferredWidth);
+      }
+    }
 
     useProxy.setSelected(Cache.getDefault("USE_PROXY", false));
-    proxyServerTB.setEnabled(useProxy.isSelected());
-    proxyPortTB.setEnabled(useProxy.isSelected());
+    useProxy_actionPerformed(); // make sure useProxy is correctly initialised
     proxyServerTB.setText(Cache.getDefault("PROXY_SERVER", ""));
     proxyPortTB.setText(Cache.getDefault("PROXY_PORT", ""));
 
@@ -554,28 +656,32 @@ public class Preferences extends GPreferences
 
     jalview.util.BrowserLauncher.resetBrowser();
 
-    if (nameLinks.size() > 0)
+    // save user-defined and selected links
+    String menuLinks = sequenceUrlLinks.writeUrlsAsString(true);
+    if (menuLinks.isEmpty())
+    {
+      Cache.applicationProperties.remove("SEQUENCE_LINKS");
+    }
+    else
     {
-      StringBuffer links = new StringBuffer();
-      sequenceURLLinks = new Vector<String>();
-      for (int i = 0; i < nameLinks.size(); i++)
-      {
-        sequenceURLLinks.addElement(nameLinks.elementAt(i) + "|"
-                + urlLinks.elementAt(i));
-        links.append(sequenceURLLinks.elementAt(i).toString());
-        links.append("|");
-      }
-      // remove last "|"
-      links.setLength(links.length() - 1);
       Cache.applicationProperties.setProperty("SEQUENCE_LINKS",
-              links.toString());
+              menuLinks.toString());
+    }
+
+    String nonMenuLinks = sequenceUrlLinks.writeUrlsAsString(false);
+    if (nonMenuLinks.isEmpty())
+    {
+      Cache.applicationProperties.remove("STORED_LINKS");
     }
     else
     {
-      Cache.applicationProperties.remove("SEQUENCE_LINKS");
-      sequenceURLLinks.clear();
+      Cache.applicationProperties.setProperty("STORED_LINKS",
+              nonMenuLinks.toString());
     }
 
+    Cache.applicationProperties.setProperty("DEFAULT_URL",
+            sequenceUrlLinks.getPrimaryUrlId());
+
     Cache.applicationProperties.setProperty("USE_PROXY",
             Boolean.toString(useProxy.isSelected()));
 
@@ -756,7 +862,6 @@ public class Preferences extends GPreferences
   @Override
   public void newLink_actionPerformed(ActionEvent e)
   {
-
     GSequenceLink link = new GSequenceLink();
     boolean valid = false;
     while (!valid)
@@ -767,10 +872,18 @@ public class Preferences extends GPreferences
       {
         if (link.checkValid())
         {
-          nameLinks.addElement(link.getName());
-          urlLinks.addElement(link.getURL());
-          updateLinkData();
-          valid = true;
+          if (((UrlLinkTableModel) linkUrlTable.getModel())
+                  .isUniqueName(link.getName()))
+          {
+            ((UrlLinkTableModel) linkUrlTable.getModel()).insertRow(
+                    link.getName(), link.getURL());
+            valid = true;
+          }
+          else
+          {
+            link.notifyDuplicate();
+            continue;
+          }
         }
       }
       else
@@ -785,36 +898,46 @@ public class Preferences extends GPreferences
   {
     GSequenceLink link = new GSequenceLink();
 
-    int index = linkNameList.getSelectedIndex();
+    int index = linkUrlTable.getSelectedRow();
     if (index == -1)
     {
-      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-              MessageManager.getString("label.no_link_selected"),
-              MessageManager.getString("label.no_link_selected"),
-              JvOptionPane.WARNING_MESSAGE);
+      // button no longer enabled if row is not selected
+      Cache.log.debug("Edit with no row selected in linkUrlTable");
       return;
     }
 
-    link.setName(nameLinks.elementAt(index).toString());
-    link.setURL(urlLinks.elementAt(index).toString());
+    int nameCol = ((UrlLinkTableModel) linkUrlTable.getModel())
+            .getNameColumn();
+    int urlCol = ((UrlLinkTableModel) linkUrlTable.getModel())
+            .getUrlColumn();
+    String oldName = linkUrlTable.getValueAt(index, nameCol).toString();
+    link.setName(oldName);
+    link.setURL(linkUrlTable.getValueAt(index, urlCol).toString());
 
     boolean valid = false;
     while (!valid)
     {
-
       if (JvOptionPane.showInternalConfirmDialog(Desktop.desktop, link,
-              MessageManager.getString("label.new_sequence_url_link"),
+              MessageManager.getString("label.edit_sequence_url_link"),
               JvOptionPane.OK_CANCEL_OPTION, -1, null) == JvOptionPane.OK_OPTION)
       {
         if (link.checkValid())
         {
-          nameLinks.setElementAt(link.getName(), index);
-          urlLinks.setElementAt(link.getURL(), index);
-          updateLinkData();
-          valid = true;
+          if ((oldName.equals(link.getName()))
+                  || (((UrlLinkTableModel) linkUrlTable.getModel())
+                          .isUniqueName(link.getName())))
+          {
+            linkUrlTable.setValueAt(link.getName(), index, nameCol);
+            linkUrlTable.setValueAt(link.getURL(), index, urlCol);
+            valid = true;
+          }
+          else
+          {
+            link.notifyDuplicate();
+            continue;
+          }
         }
       }
-
       else
       {
         break;
@@ -825,26 +948,24 @@ public class Preferences extends GPreferences
   @Override
   public void deleteLink_actionPerformed(ActionEvent e)
   {
-    int index = linkNameList.getSelectedIndex();
+    int index = linkUrlTable.getSelectedRow();
+    int modelIndex = -1;
     if (index == -1)
     {
-      JvOptionPane.showInternalMessageDialog(Desktop.desktop,
-              MessageManager.getString("label.no_link_selected"),
-              MessageManager.getString("label.no_link_selected"),
-              JvOptionPane.WARNING_MESSAGE);
+      // button no longer enabled if row is not selected
+      Cache.log.debug("Delete with no row selected in linkUrlTable");
       return;
     }
-    nameLinks.removeElementAt(index);
-    urlLinks.removeElementAt(index);
-    updateLinkData();
-  }
+    else
+    {
+      modelIndex = linkUrlTable.convertRowIndexToModel(index);
+    }
 
-  void updateLinkData()
-  {
-    linkNameList.setListData(nameLinks);
-    linkURLList.setListData(urlLinks);
+    // make sure we use the model index to delete, and not the table index
+    ((UrlLinkTableModel) linkUrlTable.getModel()).removeRow(modelIndex);
   }
 
+
   @Override
   public void defaultBrowser_mouseClicked(MouseEvent e)
   {
@@ -1061,4 +1182,45 @@ public class Preferences extends GPreferences
       return name.hashCode() + code.hashCode();
     }
   }
+  
+  private class UrlListSelectionHandler implements ListSelectionListener
+  {
+
+    @Override
+    public void valueChanged(ListSelectionEvent e)
+    {
+      ListSelectionModel lsm = (ListSelectionModel) e.getSource();
+
+      int index = lsm.getMinSelectionIndex();
+      if (index == -1)
+      {
+        // no selection, so disable delete/edit buttons
+        editLink.setEnabled(false);
+        deleteLink.setEnabled(false);
+        return;
+      }
+      int modelIndex = linkUrlTable.convertRowIndexToModel(index);
+
+      // enable/disable edit and delete link buttons
+      if (((UrlLinkTableModel) linkUrlTable.getModel())
+              .isRowDeletable(modelIndex))
+      {
+        deleteLink.setEnabled(true);
+      }
+      else
+      {
+        deleteLink.setEnabled(false);
+      }
+
+      if (((UrlLinkTableModel) linkUrlTable.getModel())
+              .isRowEditable(modelIndex))
+      {
+        editLink.setEnabled(true);
+      }
+      else
+      {
+        editLink.setEnabled(false);
+      }
+    }
+}
 }
index 165e8f2..32671d5 100644 (file)
@@ -454,7 +454,7 @@ public class WsPreferences extends GWsPreferences
     JTextField urltf = new JTextField(url, 40);
     JPanel panel = new JPanel(new BorderLayout());
     JPanel pane12 = new JPanel(new BorderLayout());
-    pane12.add(new JLabel(MessageManager.getString("label.url")),
+    pane12.add(new JLabel(MessageManager.getString("label.url:")),
             BorderLayout.CENTER);
     pane12.add(urltf, BorderLayout.EAST);
     panel.add(pane12, BorderLayout.NORTH);
@@ -574,6 +574,7 @@ public class WsPreferences extends GWsPreferences
     new Thread(new Runnable()
     {
 
+      @Override
       public void run()
       {
         // force a refresh.
@@ -599,6 +600,7 @@ public class WsPreferences extends GWsPreferences
       new Thread(new Runnable()
       {
 
+        @Override
         public void run()
         {
           progressBar.setVisible(true);
@@ -624,6 +626,7 @@ public class WsPreferences extends GWsPreferences
       new Thread(new Runnable()
       {
 
+        @Override
         public void run()
         {
           long ct = System.currentTimeMillis();
@@ -681,6 +684,7 @@ public class WsPreferences extends GWsPreferences
     new Thread(new Runnable()
     {
 
+      @Override
       public void run()
       {
         updateWsMenuConfig(false);
index 90053f5..dda06b4 100755 (executable)
@@ -45,6 +45,7 @@ import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 
+import javax.swing.AbstractCellEditor;
 import javax.swing.BorderFactory;
 import javax.swing.ButtonGroup;
 import javax.swing.DefaultListCellRenderer;
@@ -53,11 +54,11 @@ import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JFileChooser;
 import javax.swing.JLabel;
-import javax.swing.JList;
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JScrollPane;
 import javax.swing.JTabbedPane;
+import javax.swing.JTable;
 import javax.swing.JTextField;
 import javax.swing.ListSelectionModel;
 import javax.swing.SwingConstants;
@@ -67,8 +68,8 @@ import javax.swing.border.EtchedBorder;
 import javax.swing.border.TitledBorder;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableCellRenderer;
 
 /**
  * Base class for the Preferences panel.
@@ -180,7 +181,21 @@ public class GPreferences extends JPanel
   /*
    * Connections tab components
    */
-  protected JList linkURLList = new JList();
+  protected JTable linkUrlTable = new JTable();
+
+  protected JButton editLink = new JButton();
+
+  protected JButton deleteLink = new JButton();
+
+  protected JTextField filterTB = new JTextField();
+
+  protected JButton doReset = new JButton();
+
+  protected JButton userOnly = new JButton();
+
+  protected JLabel portLabel = new JLabel();
+
+  protected JLabel serverLabel = new JLabel();
 
   protected JTextField proxyServerTB = new JTextField();
 
@@ -188,8 +203,6 @@ public class GPreferences extends JPanel
 
   protected JTextField defaultBrowser = new JTextField();
 
-  protected JList linkNameList = new JList();
-
   protected JCheckBox useProxy = new JCheckBox();
 
   protected JCheckBox usagestats = new JCheckBox();
@@ -285,6 +298,9 @@ public class GPreferences extends JPanel
     tabbedPane.add(initConnectionsTab(),
             MessageManager.getString("label.connections"));
 
+    tabbedPane.add(initLinksTab(),
+            MessageManager.getString("label.urllinks"));
+
     tabbedPane.add(initOutputTab(),
             MessageManager.getString("label.output"));
 
@@ -483,40 +499,196 @@ public class GPreferences extends JPanel
   {
     JPanel connectTab = new JPanel();
     connectTab.setLayout(new GridBagLayout());
-    JLabel serverLabel = new JLabel();
-    serverLabel.setText(MessageManager.getString("label.address"));
-    serverLabel.setHorizontalAlignment(SwingConstants.RIGHT);
-    serverLabel.setFont(LABEL_FONT);
-    proxyServerTB.setFont(LABEL_FONT);
-    proxyPortTB.setFont(LABEL_FONT);
-    JLabel portLabel = new JLabel();
-    portLabel.setFont(LABEL_FONT);
-    portLabel.setHorizontalAlignment(SwingConstants.RIGHT);
-    portLabel.setText(MessageManager.getString("label.port"));
+
+    // Label for browser text box
     JLabel browserLabel = new JLabel();
-    browserLabel.setFont(new java.awt.Font("SansSerif", 0, 11));
+    browserLabel.setFont(LABEL_FONT);
     browserLabel.setHorizontalAlignment(SwingConstants.TRAILING);
     browserLabel.setText(MessageManager
             .getString("label.default_browser_unix"));
     defaultBrowser.setFont(LABEL_FONT);
     defaultBrowser.setText("");
-    usagestats.setText(MessageManager
-            .getString("label.send_usage_statistics"));
-    usagestats.setFont(LABEL_FONT);
-    usagestats.setHorizontalAlignment(SwingConstants.RIGHT);
-    usagestats.setHorizontalTextPosition(SwingConstants.LEADING);
-    questionnaire.setText(MessageManager
-            .getString("label.check_for_questionnaires"));
-    questionnaire.setFont(LABEL_FONT);
-    questionnaire.setHorizontalAlignment(SwingConstants.RIGHT);
-    questionnaire.setHorizontalTextPosition(SwingConstants.LEADING);
-    versioncheck.setText(MessageManager
-            .getString("label.check_for_latest_version"));
-    versioncheck.setFont(LABEL_FONT);
-    versioncheck.setHorizontalAlignment(SwingConstants.RIGHT);
-    versioncheck.setHorizontalTextPosition(SwingConstants.LEADING);
+
+    defaultBrowser.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mouseClicked(MouseEvent e)
+      {
+        if (e.getClickCount() > 1)
+        {
+          defaultBrowser_mouseClicked(e);
+        }
+      }
+    });
+
+    JPanel proxyPanel = initConnTabProxyPanel();
+    initConnTabCheckboxes();
+
+    // Add default Browser text box
+    connectTab.add(browserLabel, new GridBagConstraints(0, 0, 1, 1, 0.0,
+            0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
+            new Insets(10, 0, 5, 5), 5, 1));
+    defaultBrowser.setFont(LABEL_FONT);
+    defaultBrowser.setText("");
+
+    connectTab.add(defaultBrowser, new GridBagConstraints(1, 0, 1, 1, 1.0,
+            0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
+            new Insets(10, 0, 5, 10), 30, 1));
+
+    // Add proxy server panel
+    connectTab.add(proxyPanel, new GridBagConstraints(0, 1, 2, 1, 1.0, 0.0,
+            GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
+            new Insets(10, 0, 5, 12), 4, 10));
+
+    // Add usage stats, version check and questionnaire checkboxes
+    connectTab.add(usagestats, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0,
+            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
+            new Insets(0, 2, 5, 5), 70, 1));
+    connectTab.add(questionnaire, new GridBagConstraints(1, 2, 1, 1, 1.0,
+            0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
+            new Insets(0, 2, 5, 10), 70, 1));
+    connectTab.add(versioncheck, new GridBagConstraints(0, 3, 1, 1, 1.0,
+            0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
+            new Insets(0, 2, 5, 5), 70, 1));
+
+    // Add padding so the panel doesn't look ridiculous
+    JPanel spacePanel = new JPanel();
+    connectTab.add(spacePanel, new GridBagConstraints(0, 4, 1, 1, 1.0, 1.0,
+            GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0,
+                    0, 0, 5), 70, 1));
+
+    return connectTab;
+  }
+
+  /**
+   * Initialises the Links tabbed panel.
+   * 
+   * @return
+   */
+  private JPanel initLinksTab()
+  {
+    JPanel linkTab = new JPanel();
+    linkTab.setLayout(new GridBagLayout());
+
+    // Set up table for Url links
+    linkUrlTable.setFillsViewportHeight(true);
+    linkUrlTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
+    linkUrlTable.setAutoCreateRowSorter(true);
+    linkUrlTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+    // adjust row height so radio buttons actually fit
+    // don't do this in the renderer, it causes the awt thread to activate
+    // constantly
+    JRadioButton temp = new JRadioButton();
+    linkUrlTable.setRowHeight(temp.getMinimumSize().height);
+
+    // Table in scrollpane so that the table is given a scrollbar
+    JScrollPane linkScrollPane = new JScrollPane(linkUrlTable);
+    linkScrollPane.setBorder(null);
+
+    // Panel for links functionality
+    JPanel linkPanel = new JPanel(new GridBagLayout());
+    linkPanel.setBorder(new TitledBorder(MessageManager
+            .getString("label.url_linkfrom_sequence_id")));
+
+    // Put the Url links panel together
+
+    // Buttons go at top right, resizing only resizes the blank space vertically
+    JPanel buttonPanel = initLinkTabUrlButtons();
+    GridBagConstraints linkConstraints1 = new GridBagConstraints();
+    linkConstraints1.insets = new Insets(0, 0, 5, 0);
+    linkConstraints1.gridx = 0;
+    linkConstraints1.gridy = 0;
+    linkConstraints1.weightx = 1.0;
+    linkConstraints1.fill = GridBagConstraints.HORIZONTAL;
+    linkTab.add(buttonPanel, linkConstraints1);
+
+    // Links table goes at top left, resizing resizes the table
+    GridBagConstraints linkConstraints2 = new GridBagConstraints();
+    linkConstraints2.insets = new Insets(0, 0, 5, 5);
+    linkConstraints2.gridx = 0;
+    linkConstraints2.gridy = 1;
+    linkConstraints2.weightx = 1.0;
+    linkConstraints2.weighty = 1.0;
+    linkConstraints2.fill = GridBagConstraints.BOTH;
+    linkTab.add(linkScrollPane, linkConstraints2);
+
+    // Filter box and buttons goes at bottom left, resizing resizes the text box
+    JPanel filterPanel = initLinkTabFilterPanel();
+    GridBagConstraints linkConstraints3 = new GridBagConstraints();
+    linkConstraints3.insets = new Insets(0, 0, 0, 5);
+    linkConstraints3.gridx = 0;
+    linkConstraints3.gridy = 2;
+    linkConstraints3.weightx = 1.0;
+    linkConstraints3.fill = GridBagConstraints.HORIZONTAL;
+    linkTab.add(filterPanel, linkConstraints3);
+
+    return linkTab;
+  }
+
+  private JPanel initLinkTabFilterPanel()
+  {
+    // Filter textbox and reset button
+    JLabel filterLabel = new JLabel(
+            MessageManager.getString("label.filter"));
+    filterLabel.setFont(LABEL_FONT);
+    filterLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+    filterLabel.setHorizontalTextPosition(SwingConstants.LEADING);
+
+    filterTB.setFont(LABEL_FONT);
+    filterTB.setText("");
+
+    doReset.setText(MessageManager.getString("action.showall"));
+    userOnly.setText(MessageManager.getString("action.customfilter"));
+
+    // Panel for filter functionality
+    JPanel filterPanel = new JPanel(new GridBagLayout());
+    filterPanel.setBorder(new TitledBorder("Filter"));
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridx = 0;
+    gbc.gridy = 0;
+    gbc.fill = GridBagConstraints.NONE;
+    gbc.anchor = GridBagConstraints.WEST;
+
+    filterPanel.add(filterLabel, gbc);
+
+    GridBagConstraints gbc1 = new GridBagConstraints();
+    gbc1.gridx = 1;
+    gbc1.gridwidth = 2;
+    gbc1.fill = GridBagConstraints.HORIZONTAL;
+    gbc1.anchor = GridBagConstraints.WEST;
+    gbc1.weightx = 1.0;
+    filterPanel.add(filterTB, gbc1);
+
+    GridBagConstraints gbc2 = new GridBagConstraints();
+    gbc2.gridx = 3;
+    gbc2.fill = GridBagConstraints.NONE;
+    gbc2.anchor = GridBagConstraints.WEST;
+    filterPanel.add(doReset, gbc2);
+
+    GridBagConstraints gbc3 = new GridBagConstraints();
+    gbc3.gridx = 4;
+    gbc3.fill = GridBagConstraints.NONE;
+    gbc3.anchor = GridBagConstraints.WEST;
+    filterPanel.add(userOnly, gbc3);
+
+    return filterPanel;
+  }
+
+  private JPanel initLinkTabUrlButtons()
+  {
+    // Buttons for new / edit / delete Url links
     JButton newLink = new JButton();
     newLink.setText(MessageManager.getString("action.new"));
+
+    editLink.setText(MessageManager.getString("action.edit"));
+
+    deleteLink.setText(MessageManager.getString("action.delete"));
+
+    // no current selection, so initially disable delete/edit buttons
+    editLink.setEnabled(false);
+    deleteLink.setEnabled(false);
+    
     newLink.addActionListener(new java.awt.event.ActionListener()
     {
       @Override
@@ -525,7 +697,7 @@ public class GPreferences extends JPanel
         newLink_actionPerformed(e);
       }
     });
-    JButton editLink = new JButton();
+
     editLink.setText(MessageManager.getString("action.edit"));
     editLink.addActionListener(new java.awt.event.ActionListener()
     {
@@ -535,7 +707,7 @@ public class GPreferences extends JPanel
         editLink_actionPerformed(e);
       }
     });
-    JButton deleteLink = new JButton();
+
     deleteLink.setText(MessageManager.getString("action.delete"));
     deleteLink.addActionListener(new java.awt.event.ActionListener()
     {
@@ -546,55 +718,60 @@ public class GPreferences extends JPanel
       }
     });
 
-    linkURLList.addListSelectionListener(new ListSelectionListener()
-    {
-      @Override
-      public void valueChanged(ListSelectionEvent e)
-      {
-        int index = linkURLList.getSelectedIndex();
-        linkNameList.setSelectedIndex(index);
-      }
-    });
+    JPanel buttonPanel = new JPanel(new GridBagLayout());
+    buttonPanel.setBorder(new TitledBorder("Edit links"));
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridx = 0;
+    gbc.gridy = 0;
+    gbc.fill = GridBagConstraints.NONE;
+    buttonPanel.add(newLink, gbc);
+
+    GridBagConstraints gbc1 = new GridBagConstraints();
+    gbc1.gridx = 1;
+    gbc1.gridy = 0;
+    gbc1.fill = GridBagConstraints.NONE;
+    buttonPanel.add(editLink, gbc1);
+
+    GridBagConstraints gbc2 = new GridBagConstraints();
+    gbc2.gridx = 2;
+    gbc2.gridy = 0;
+    gbc2.fill = GridBagConstraints.NONE;
+    buttonPanel.add(deleteLink, gbc2);
+
+    GridBagConstraints gbc3 = new GridBagConstraints();
+    gbc3.gridx = 3;
+    gbc3.gridy = 0;
+    gbc3.fill = GridBagConstraints.HORIZONTAL;
+    gbc3.weightx = 1.0;
+    JPanel spacePanel = new JPanel();
+    spacePanel.setBorder(null);
+    buttonPanel.add(spacePanel, gbc3);
+
+    return buttonPanel;
+  }
 
-    linkNameList.addListSelectionListener(new ListSelectionListener()
-    {
-      @Override
-      public void valueChanged(ListSelectionEvent e)
-      {
-        int index = linkNameList.getSelectedIndex();
-        linkURLList.setSelectedIndex(index);
-      }
-    });
+  /**
+   * Initialises the proxy server panel in the Connections tab
+   * 
+   * @return the proxy server panel
+   */
+  private JPanel initConnTabProxyPanel()
+  {
+    // Label for server text box
+    serverLabel.setText(MessageManager.getString("label.address"));
+    serverLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+    serverLabel.setFont(LABEL_FONT);
 
-    JScrollPane linkScrollPane = new JScrollPane();
-    linkScrollPane.setBorder(null);
-    JPanel linkPanel = new JPanel();
-    linkPanel.setBorder(new TitledBorder(MessageManager
-            .getString("label.url_linkfrom_sequence_id")));
-    linkPanel.setLayout(new BorderLayout());
-    GridLayout gridLayout1 = new GridLayout();
-    JPanel editLinkButtons = new JPanel();
-    editLinkButtons.setLayout(gridLayout1);
-    gridLayout1.setRows(3);
-    linkNameList.setFont(LABEL_FONT);
-    linkNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-    BorderLayout borderLayout3 = new BorderLayout();
-    JPanel linkPanel2 = new JPanel();
-    linkPanel2.setLayout(borderLayout3);
-    linkURLList.setFont(LABEL_FONT);
-    linkURLList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+    // Proxy server and port text boxes
+    proxyServerTB.setFont(LABEL_FONT);
+    proxyPortTB.setFont(LABEL_FONT);
 
-    defaultBrowser.addMouseListener(new MouseAdapter()
-    {
-      @Override
-      public void mouseClicked(MouseEvent e)
-      {
-        if (e.getClickCount() > 1)
-        {
-          defaultBrowser_mouseClicked(e);
-        }
-      }
-    });
+    // Label for Port text box
+    portLabel.setFont(LABEL_FONT);
+    portLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+    portLabel.setText(MessageManager.getString("label.port"));
+
+    // Use proxy server checkbox
     useProxy.setFont(LABEL_FONT);
     useProxy.setHorizontalAlignment(SwingConstants.RIGHT);
     useProxy.setHorizontalTextPosition(SwingConstants.LEADING);
@@ -607,56 +784,57 @@ public class GPreferences extends JPanel
         useProxy_actionPerformed();
       }
     });
-    linkPanel.add(editLinkButtons, BorderLayout.EAST);
-    editLinkButtons.add(newLink, null);
-    editLinkButtons.add(editLink, null);
-    editLinkButtons.add(deleteLink, null);
-    linkPanel.add(linkScrollPane, BorderLayout.CENTER);
-    linkScrollPane.getViewport().add(linkPanel2, null);
-    linkPanel2.add(linkURLList, BorderLayout.CENTER);
-    linkPanel2.add(linkNameList, BorderLayout.WEST);
-    JPanel jPanel1 = new JPanel();
+
+    // Make proxy server panel
+    JPanel proxyPanel = new JPanel();
     TitledBorder titledBorder1 = new TitledBorder(
             MessageManager.getString("label.proxy_server"));
-    jPanel1.setBorder(titledBorder1);
-    jPanel1.setLayout(new GridBagLayout());
-    jPanel1.add(serverLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
-            GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,
-                    2, 4, 0), 5, 0));
-    jPanel1.add(portLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
-            GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,
-                    0, 4, 0), 11, 6));
-    connectTab.add(linkPanel, new GridBagConstraints(0, 0, 2, 1, 1.0, 1.0,
-            GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(
-                    16, 0, 0, 12), 359, -17));
-    connectTab.add(jPanel1, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0,
-            GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(
-                    21, 0, 35, 12), 4, 6));
-    connectTab.add(browserLabel, new GridBagConstraints(0, 1, 1, 1, 0.0,
+    proxyPanel.setBorder(titledBorder1);
+    proxyPanel.setLayout(new GridBagLayout());
+    proxyPanel.add(serverLabel, new GridBagConstraints(0, 1, 1, 1, 0.0,
             0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
-            new Insets(16, 0, 0, 0), 5, 1));
-    jPanel1.add(useProxy, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0,
+            new Insets(0, 2, 2, 0), 5, 0));
+    proxyPanel.add(portLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
+            GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,
+                    0, 2, 0), 11, 0));
+    proxyPanel.add(useProxy, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0,
             GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,
                     2, 5, 185), 2, -4));
-    jPanel1.add(proxyPortTB, new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0,
-            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-            new Insets(0, 2, 4, 2), 54, 1));
-    jPanel1.add(proxyServerTB, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0,
-            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-            new Insets(0, 2, 4, 0), 263, 1));
-    connectTab.add(defaultBrowser, new GridBagConstraints(1, 1, 1, 1, 1.0,
-            0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-            new Insets(15, 0, 0, 15), 307, 1));
-    connectTab.add(usagestats, new GridBagConstraints(0, 4, 1, 1, 1.0, 0.0,
-            GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-            new Insets(0, 2, 4, 2), 70, 1));
-    connectTab.add(questionnaire, new GridBagConstraints(1, 4, 1, 1, 1.0,
+    proxyPanel.add(proxyPortTB, new GridBagConstraints(3, 1, 1, 1, 1.0,
             0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-            new Insets(0, 2, 4, 2), 70, 1));
-    connectTab.add(versioncheck, new GridBagConstraints(0, 5, 1, 1, 1.0,
+            new Insets(0, 2, 2, 2), 54, 1));
+    proxyPanel.add(proxyServerTB, new GridBagConstraints(1, 1, 1, 1, 1.0,
             0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
-            new Insets(0, 2, 4, 2), 70, 1));
-    return connectTab;
+            new Insets(0, 2, 2, 0), 263, 1));
+
+    return proxyPanel;
+  }
+
+  /**
+   * Initialises the checkboxes in the Connections tab
+   */
+  private void initConnTabCheckboxes()
+  {
+    // Usage stats checkbox label
+    usagestats.setText(MessageManager
+            .getString("label.send_usage_statistics"));
+    usagestats.setFont(LABEL_FONT);
+    usagestats.setHorizontalAlignment(SwingConstants.RIGHT);
+    usagestats.setHorizontalTextPosition(SwingConstants.LEADING);
+
+    // Questionnaire checkbox label
+    questionnaire.setText(MessageManager
+            .getString("label.check_for_questionnaires"));
+    questionnaire.setFont(LABEL_FONT);
+    questionnaire.setHorizontalAlignment(SwingConstants.RIGHT);
+    questionnaire.setHorizontalTextPosition(SwingConstants.LEADING);
+
+    // Check for latest version checkbox label
+    versioncheck.setText(MessageManager
+            .getString("label.check_for_latest_version"));
+    versioncheck.setFont(LABEL_FONT);
+    versioncheck.setHorizontalAlignment(SwingConstants.RIGHT);
+    versioncheck.setHorizontalTextPosition(SwingConstants.LEADING);
   }
 
   /**
@@ -1357,8 +1535,82 @@ public class GPreferences extends JPanel
 
   public void useProxy_actionPerformed()
   {
-    proxyServerTB.setEnabled(useProxy.isSelected());
-    proxyPortTB.setEnabled(useProxy.isSelected());
+    boolean enabled = useProxy.isSelected();
+    portLabel.setEnabled(enabled);
+    serverLabel.setEnabled(enabled);
+    proxyServerTB.setEnabled(enabled);
+    proxyPortTB.setEnabled(enabled);
+  }
+
+  /**
+   * Customer renderer for JTable: supports column of radio buttons
+   */
+  public class RadioButtonRenderer extends JRadioButton implements
+          TableCellRenderer
+  {
+    public RadioButtonRenderer()
+    {
+      setHorizontalAlignment(CENTER);
+      setToolTipText(MessageManager.getString("label.urltooltip"));
+    }
+
+    @Override
+    public Component getTableCellRendererComponent(JTable table,
+            Object value, boolean isSelected, boolean hasFocus, int row,
+            int column)
+    {
+      setSelected((boolean) value);
+
+      // set colours to match rest of table
+      if (isSelected)
+       {
+         setBackground(table.getSelectionBackground());
+         setForeground(table.getSelectionForeground());
+       }
+       else
+       {
+         setBackground(table.getBackground());
+         setForeground(table.getForeground());
+      }
+      return this;
+    }
   }
 
+  /**
+   * Customer cell editor for JTable: supports column of radio buttons in
+   * conjunction with renderer
+   */
+  public class RadioButtonEditor extends AbstractCellEditor implements
+            TableCellEditor
+    {
+      private JRadioButton button = new JRadioButton();
+
+      public RadioButtonEditor()
+      {
+      button.setHorizontalAlignment(SwingConstants.CENTER);
+      this.button.addActionListener(new ActionListener()
+        {
+          @Override
+          public void actionPerformed(ActionEvent e)
+          {
+            fireEditingStopped();
+          }
+        });
+      }
+
+      @Override
+      public Component getTableCellEditorComponent(JTable table,
+              Object value, boolean isSelected, int row, int column)
+      {
+      button.setSelected((boolean) value);
+        return button;
+      }
+
+      @Override
+      public Object getCellEditorValue()
+      {
+      return button.isSelected();
+      }
+
+  }
 }
index dbce5f3..ab3ea2c 100755 (executable)
@@ -29,19 +29,48 @@ import java.awt.Font;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.Insets;
-import java.awt.Panel;
 import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 
 import javax.swing.BorderFactory;
+import javax.swing.JButton;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
 
-public class GSequenceLink extends Panel
+public class GSequenceLink extends JPanel
 {
+
+  JTextField nameTB = new JTextField();
+
+  JTextField urlTB = new JTextField();
+
+  JButton insertSeq = new JButton();
+
+  JButton insertDBAcc = new JButton();
+
+  JLabel insert = new JLabel();
+
+  JLabel jLabel1 = new JLabel();
+
+  JLabel jLabel2 = new JLabel();
+
+  JLabel jLabel3 = new JLabel();
+
+  JLabel jLabel4 = new JLabel();
+
+  JLabel jLabel5 = new JLabel();
+
+  JLabel jLabel6 = new JLabel();
+
+  JPanel jPanel1 = new JPanel();
+
+  GridBagLayout gridBagLayout1 = new GridBagLayout();
+
   public GSequenceLink()
   {
     try
@@ -77,23 +106,53 @@ public class GSequenceLink extends Panel
         urlTB_keyTyped(e);
       }
     });
+
+    insertSeq.setLocation(77, 75);
+    insertSeq.setSize(141, 24);
+    insertSeq.setText(MessageManager.getString("action.seq_id"));
+    insertSeq.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        insertSeq_action(e);
+      }
+    });
+
+    insertDBAcc.setLocation(210, 75);
+    insertDBAcc.setSize(141, 24);
+    insertDBAcc.setText(MessageManager.getString("action.db_acc"));
+    insertDBAcc.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        insertDBAcc_action(e);
+      }
+    });
+
+    insert.setText(MessageManager.getString("label.insert"));
+    insert.setFont(JvSwingUtils.getLabelFont());
+    insert.setHorizontalAlignment(SwingConstants.RIGHT);
+    insert.setBounds(17, 78, 58, 16);
+
     jLabel1.setFont(JvSwingUtils.getLabelFont());
     jLabel1.setHorizontalAlignment(SwingConstants.TRAILING);
     jLabel1.setText(MessageManager.getString("label.link_name"));
     jLabel1.setBounds(new Rectangle(4, 10, 71, 24));
     jLabel2.setFont(JvSwingUtils.getLabelFont());
     jLabel2.setHorizontalAlignment(SwingConstants.TRAILING);
-    jLabel2.setText(MessageManager.getString("label.url"));
+    jLabel2.setText(MessageManager.getString("label.url:"));
     jLabel2.setBounds(new Rectangle(17, 37, 54, 27));
     jLabel3.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
     jLabel3.setText(MessageManager.getString("label.use_sequence_id_1"));
-    jLabel3.setBounds(new Rectangle(21, 72, 351, 15));
+    jLabel3.setBounds(new Rectangle(21, 102, 351, 15));
     jLabel4.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
     jLabel4.setText(MessageManager.getString("label.use_sequence_id_2"));
-    jLabel4.setBounds(new Rectangle(21, 88, 351, 15));
+    jLabel4.setBounds(new Rectangle(21, 118, 351, 15));
     jLabel5.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
     jLabel5.setText(MessageManager.getString("label.use_sequence_id_3"));
-    jLabel5.setBounds(new Rectangle(21, 106, 351, 15));
+    jLabel5.setBounds(new Rectangle(21, 136, 351, 15));
 
     String lastLabel = MessageManager.getString("label.use_sequence_id_4");
     if (lastLabel.length() > 0)
@@ -101,7 +160,7 @@ public class GSequenceLink extends Panel
       // e.g. Spanish version has longer text
       jLabel6.setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
       jLabel6.setText(lastLabel);
-      jLabel6.setBounds(new Rectangle(21, 122, 351, 15));
+      jLabel6.setBounds(new Rectangle(21, 152, 351, 15));
     }
 
     jPanel1.setBorder(BorderFactory.createEtchedBorder());
@@ -109,16 +168,19 @@ public class GSequenceLink extends Panel
     jPanel1.add(jLabel1);
     jPanel1.add(nameTB);
     jPanel1.add(urlTB);
+    jPanel1.add(insertSeq);
+    jPanel1.add(insertDBAcc);
+    jPanel1.add(insert);
     jPanel1.add(jLabel2);
     jPanel1.add(jLabel3);
     jPanel1.add(jLabel4);
     jPanel1.add(jLabel5);
 
-    int height = 130;
+    int height = 160;
     if (lastLabel.length() > 0)
     {
       jPanel1.add(jLabel6);
-      height = 146;
+      height = 176;
     }
 
     this.add(jPanel1, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0,
@@ -163,25 +225,13 @@ public class GSequenceLink extends Panel
     return false;
   }
 
-  JTextField nameTB = new JTextField();
-
-  JTextField urlTB = new JTextField();
-
-  JLabel jLabel1 = new JLabel();
-
-  JLabel jLabel2 = new JLabel();
-
-  JLabel jLabel3 = new JLabel();
-
-  JLabel jLabel4 = new JLabel();
-
-  JLabel jLabel5 = new JLabel();
-
-  JLabel jLabel6 = new JLabel();
-
-  JPanel jPanel1 = new JPanel();
-
-  GridBagLayout gridBagLayout1 = new GridBagLayout();
+  public void notifyDuplicate()
+  {
+    JvOptionPane.showInternalMessageDialog(jalview.gui.Desktop.desktop,
+            MessageManager.getString("warn.name_cannot_be_duplicate"),
+            MessageManager.getString("label.invalid_name"),
+            JvOptionPane.WARNING_MESSAGE);
+  }
 
   public void nameTB_keyTyped(KeyEvent e)
   {
@@ -200,4 +250,23 @@ public class GSequenceLink extends Panel
     // }
 
   }
+
+  public void insertSeq_action(ActionEvent e)
+  {
+    insertIntoUrl(insertSeq.getText());
+  }
+
+  public void insertDBAcc_action(ActionEvent e)
+  {
+    insertIntoUrl(insertDBAcc.getText());
+  }
+
+  private void insertIntoUrl(String insertion)
+  {
+    int pos = urlTB.getCaretPosition();
+    String text = urlTB.getText();
+    String newText = text.substring(0, pos) + insertion
+            + text.substring(pos);
+    urlTB.setText(newText);
+  }
 }
diff --git a/src/jalview/urls/CustomUrlProvider.java b/src/jalview/urls/CustomUrlProvider.java
new file mode 100644 (file)
index 0000000..07f4068
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.urls;
+
+import static jalview.util.UrlConstants.DB_ACCESSION;
+import static jalview.util.UrlConstants.DELIM;
+import static jalview.util.UrlConstants.SEP;
+import static jalview.util.UrlConstants.SEQUENCE_ID;
+
+import jalview.util.MessageManager;
+import jalview.util.UrlConstants;
+import jalview.util.UrlLink;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.StringTokenizer;
+
+/**
+ * 
+ * Implements the UrlProviderI interface for a UrlProvider object which serves
+ * custom URLs defined by the user
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class CustomUrlProvider extends UrlProviderImpl
+{
+  // Default sequence URL link label for SRS
+  private static final String SRS_LABEL = "SRS";
+
+  // map of string ids to urlLinks (selected)
+  private HashMap<String, UrlLink> selectedUrls;
+
+  // map of string ids to urlLinks (not selected)
+  private HashMap<String, UrlLink> nonselectedUrls;
+
+  /**
+   * Construct UrlProvider for custom (user-entered) URLs
+   * 
+   * @param inMenuUrlList
+   *          list of URLs set to be displayed in menu, in form stored in Cache.
+   *          i.e. SEP delimited string
+   * @param storedUrlList
+   *          list of custom URLs entered by user but not currently displayed in
+   *          menu, in form stored in Cache
+   */
+  public CustomUrlProvider(String inMenuUrlList, String storedUrlList)
+  {
+    try
+    {
+      selectedUrls = parseUrlStrings(inMenuUrlList);
+      nonselectedUrls = parseUrlStrings(storedUrlList);
+    } catch (Exception ex)
+    {
+      System.out
+              .println(ex.getMessage() + "\nError parsing sequence links");
+    }
+  }
+
+  /**
+   * Construct UrlProvider for custom (user-entered) URLs
+   * 
+   * @param urlList
+   *          list of URLs to be displayed in menu, as (label,url) pairs
+   * @param storedUrlList
+   *          list of custom URLs entered by user but not currently displayed in
+   *          menu, as (label,url) pairs
+   */
+  public CustomUrlProvider(Map<String, String> inMenuUrlList,
+          Map<String, String> storedUrlList)
+  {
+    try
+    {
+      selectedUrls = parseUrlList(inMenuUrlList);
+      nonselectedUrls = parseUrlList(storedUrlList);
+    } catch (Exception ex)
+    {
+      System.out
+              .println(ex.getMessage() + "\nError parsing sequence links");
+    }
+  }
+
+  private HashMap<String, UrlLink> parseUrlStrings(String urlStrings)
+  {
+    // 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>();
+
+    StringTokenizer st = new StringTokenizer(urlStrings, SEP);
+    while (st.hasMoreElements())
+    {
+      String name = st.nextToken();
+
+      if (!isMiriamId(name))
+      {
+        // this one of our custom urls
+        String url = st.nextToken();
+        // check for '|' within a regex
+        int rxstart = url.indexOf(DELIM + DB_ACCESSION + DELIM);
+        if (rxstart == -1)
+        {
+          rxstart = url.indexOf(DELIM + SEQUENCE_ID + DELIM);
+        }
+        while (rxstart == -1 && url.indexOf("/=" + DELIM) == -1
+                && st.hasMoreTokens())
+        {
+          url = url + SEP + st.nextToken();
+        }
+        urls.put(name, new UrlLink(name, url, name));
+      }
+    }
+    upgradeOldLinks(urls);
+    return urls;
+  }
+
+  private HashMap<String, UrlLink> parseUrlList(Map<String, String> urlList)
+  {
+    HashMap<String, UrlLink> urls = new HashMap<String, UrlLink>();
+    if (urlList == null)
+    {
+      return urls;
+    }
+
+    Iterator<Map.Entry<String, String>> it = urlList.entrySet().iterator();
+    while (it.hasNext())
+    {
+      Map.Entry<String, String> pair = it.next();
+      urls.put(pair.getKey(),
+ new UrlLink(pair.getKey(), pair.getValue(),
+              pair.getKey()));
+    }
+    upgradeOldLinks(urls);
+    return urls;
+  }
+
+  /*
+   * Upgrade any legacy links which may have been left lying around
+   */
+  private void upgradeOldLinks(HashMap<String, UrlLink> urls)
+  {
+    // upgrade old SRS link
+    if (urls.containsKey(SRS_LABEL))
+    {
+      urls.remove(SRS_LABEL);
+      UrlLink link = new UrlLink(UrlConstants.DEFAULT_STRING);
+      link.setLabel(UrlConstants.DEFAULT_LABEL);
+      urls.put(UrlConstants.DEFAULT_LABEL, link);
+    }
+  }
+
+  @Override
+  public List<String> getLinksForMenu()
+  {
+    List<String> links = new ArrayList<String>();
+    Iterator<Map.Entry<String, UrlLink>> it = selectedUrls.entrySet()
+            .iterator();
+    while (it.hasNext())
+    {
+      Map.Entry<String, UrlLink> pair = it.next();
+      links.add(pair.getValue().toString());
+    }
+    return links;
+  }
+
+  @Override
+  public List<UrlLinkDisplay> getLinksForTable()
+  {
+    ArrayList<UrlLinkDisplay> displayLinks = new ArrayList<UrlLinkDisplay>();
+    displayLinks = getLinksForTable(selectedUrls, true);
+    displayLinks.addAll(getLinksForTable(nonselectedUrls, false));
+    return displayLinks;
+  }
+
+  private ArrayList<UrlLinkDisplay> getLinksForTable(
+          HashMap<String, UrlLink> urlList, boolean selected)
+  {
+    return super.getLinksForTable(urlList, null, selected);
+  }
+
+  @Override
+  public boolean setPrimaryUrl(String id)
+  {
+    if (selectedUrls.containsKey(id))
+    {
+      primaryUrl = id;
+    }
+    else if (nonselectedUrls.containsKey(id))
+    {
+      primaryUrl = id;
+    }
+    else
+    {
+      primaryUrl = null;
+    }
+
+    return (primaryUrl != null);
+  }
+
+  @Override
+  public String writeUrlsAsString(boolean selected)
+  {
+    StringBuffer links = new StringBuffer();
+    HashMap<String, UrlLink> urls;
+    if (selected)
+    {
+      urls = selectedUrls;
+    }
+    else
+    {
+      urls = nonselectedUrls;
+    }
+    if (urls.size() > 0)
+    {
+      for (Entry<String, UrlLink> entry : urls.entrySet())
+      {
+        links.append(entry.getValue().toString());
+        links.append(SEP);
+      }
+
+      // remove last SEP
+      links.setLength(links.length() - 1);
+    }
+    else
+    {
+      urls.clear();
+    }
+    return links.toString();
+  }
+
+  @Override
+  public String getPrimaryUrl(String seqid)
+  {
+    String result = super.getPrimaryUrl(seqid, selectedUrls);
+    if (result == null)
+    {
+      result = super.getPrimaryUrl(seqid, nonselectedUrls);
+    }
+    return result;
+  }
+
+  @Override
+  public String getPrimaryUrlId()
+  {
+    return primaryUrl;
+  }
+
+  @Override
+  public String getPrimaryTarget(String seqid)
+  {
+    return selectedUrls.get(primaryUrl).getTarget();
+  }
+
+  @Override
+  public void setUrlData(List<UrlLinkDisplay> links)
+  {
+    HashMap<String, UrlLink> unselurls = new HashMap<String, UrlLink>();
+    HashMap<String, UrlLink> selurls = new HashMap<String, UrlLink>();
+
+    Iterator<UrlLinkDisplay> it = links.iterator();
+    while (it.hasNext())
+    {
+      UrlLinkDisplay link = it.next();
+
+      // MIRIAM ids will be handled by a different UrlProvider class
+      if (!isMiriamId(link.getId()))
+      {
+        // don't allow duplicate key names as entries will be overwritten
+        if (unselurls.containsKey(link.getId())
+                || selurls.containsKey(link.getId()))
+        {
+          throw new IllegalArgumentException(MessageManager.formatMessage(
+                  "exception.url_cannot_have_duplicate_id", link.getId()));
+        }
+        if (link.getIsSelected())
+        {
+          selurls.put(link.getId(),
+                  new UrlLink(link.getDescription(), link.getUrl(), link.getDescription()));
+        }
+        else
+        {
+          unselurls
+                  .put(link.getId(),
+                          new UrlLink(link.getDescription(), link.getUrl(), link
+                                  .getDescription()));
+        }
+        // sort out primary and selected ids
+        if (link.getIsPrimary())
+        {
+          setPrimaryUrl(link.getId());
+        }
+      }
+
+    }
+    nonselectedUrls = unselurls;
+    selectedUrls = selurls;
+  }
+
+  @Override
+  public String choosePrimaryUrl()
+  {
+    // unilaterally set the primary id to the EMBL_EBI link
+    if ((!nonselectedUrls.containsKey(UrlConstants.DEFAULT_LABEL))
+            && (!selectedUrls.containsKey(UrlConstants.DEFAULT_LABEL)))
+    {
+      UrlLink link = new UrlLink(UrlConstants.DEFAULT_STRING);
+      link.setLabel(UrlConstants.DEFAULT_LABEL);
+      selectedUrls.put(UrlConstants.DEFAULT_LABEL, link);
+    }
+    primaryUrl = UrlConstants.DEFAULT_LABEL;
+    return UrlConstants.DEFAULT_LABEL;
+  }
+
+  @Override
+  public boolean contains(String id)
+  {
+    return (selectedUrls.containsKey(id) || nonselectedUrls.containsKey(id));
+  }
+
+}
diff --git a/src/jalview/urls/IdOrgSettings.java b/src/jalview/urls/IdOrgSettings.java
new file mode 100644 (file)
index 0000000..7dd1a19
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.urls;
+
+/**
+ * Holds settings for identifiers.org e.g. url, download location
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class IdOrgSettings
+{
+  private static String url;
+
+  private static String location;
+
+  public static void setUrl(String seturl)
+  {
+    url = seturl;
+  }
+
+  public static void setDownloadLocation(String setloc)
+  {
+    location = setloc;
+  }
+
+  public static String getUrl()
+  {
+    return url;
+  }
+
+  public static String getDownloadLocation()
+  {
+    return location;
+  }
+}
diff --git a/src/jalview/urls/IdentifiersUrlProvider.java b/src/jalview/urls/IdentifiersUrlProvider.java
new file mode 100644 (file)
index 0000000..c938666
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.urls;
+
+import static jalview.util.UrlConstants.DB_ACCESSION;
+import static jalview.util.UrlConstants.DELIM;
+import static jalview.util.UrlConstants.SEP;
+
+import jalview.util.UrlLink;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+/**
+ * 
+ * Implements the UrlProviderI interface for a UrlProvider object which serves
+ * URLs from identifiers.org
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class IdentifiersUrlProvider extends UrlProviderImpl
+{
+
+  private static final String LOCAL_KEY = "Local";
+
+  private static final String ID_ORG_KEY = "identifiers.org";
+
+  // map of string ids to urls
+  private HashMap<String, UrlLink> urls;
+
+  // list of selected urls
+  private ArrayList<String> selectedUrls;
+
+  public IdentifiersUrlProvider(String cachedUrlList)
+  {
+    urls = readIdentifiers(IdOrgSettings.getDownloadLocation());
+    selectedUrls = new ArrayList<String>();
+    checkSelectionMatchesUrls(cachedUrlList);
+  }
+
+  /**
+   * Read data from an identifiers.org download file
+   * 
+   * @param idFileName
+   *          name of identifiers.org download file
+   * @return hashmap of identifiers.org data, keyed by MIRIAM id
+   */
+  private HashMap<String, UrlLink> readIdentifiers(
+          String idFileName)
+  {
+    JSONParser parser = new JSONParser();
+
+    // identifiers.org data
+    HashMap<String, UrlLink> idData = new HashMap<String, UrlLink>();
+
+    try
+    {
+      FileReader reader = new FileReader(idFileName);
+      String key = "";
+      JSONObject obj = (JSONObject) parser.parse(reader);
+      if (obj.containsKey(ID_ORG_KEY))
+      {
+        key = ID_ORG_KEY;
+      }
+      else if (obj.containsKey(LOCAL_KEY))
+      {
+        key = LOCAL_KEY;
+      }
+      else
+      {
+        System.out
+                .println("Unexpected key returned from identifiers jalview service");
+        return idData;
+      }
+
+      JSONArray jsonarray = (JSONArray) obj.get(key);
+
+      // loop over each entry in JSON array and build HashMap entry
+      for (int i = 0; i < jsonarray.size(); i++)
+      {
+        JSONObject item = (JSONObject) jsonarray.get(i);
+
+        String url = (String) item.get("url") + "/" + DELIM + DB_ACCESSION
+                + DELIM;
+        UrlLink link = new UrlLink((String) item.get("name"), url,
+                (String) item.get("prefix"));
+        idData.put((String) item.get("id"), link);
+      }
+    } catch (FileNotFoundException e)
+    {
+      e.printStackTrace();
+      idData.clear();
+    } catch (IOException e)
+    {
+      e.printStackTrace();
+      idData.clear();
+    } catch (ParseException e)
+    {
+      e.printStackTrace();
+      idData.clear();
+    }
+    return idData;
+  }
+
+  private void checkSelectionMatchesUrls(String cachedUrlList)
+  {
+    StringTokenizer st = new StringTokenizer(cachedUrlList, SEP);
+    while (st.hasMoreElements())
+    {
+      String id = st.nextToken();
+
+      if (isMiriamId(id))
+      {
+        // this is an identifiers.org MIRIAM id
+        if (urls.containsKey(id))
+        {
+          selectedUrls.add(id);
+        }
+      }
+    }
+
+    // reset defaultUrl in case it is no longer selected
+    setPrimaryUrl(primaryUrl);
+  }
+
+  @Override
+  public boolean setPrimaryUrl(String id)
+  {
+    if (urls.containsKey(id))
+    {
+      primaryUrl = id;
+    }
+    else
+    {
+      primaryUrl = null;
+    }
+
+    return urls.containsKey(id);
+  }
+
+  @Override
+  public String writeUrlsAsString(boolean selected)
+  {
+    if (!selected)
+    {
+      return ""; // we don't cache unselected identifiers.org urls
+    }
+
+    StringBuffer links = new StringBuffer();
+    if (!selectedUrls.isEmpty())
+    {
+      for (String k : selectedUrls)
+      {
+        links.append(k);
+        links.append(SEP);
+      }
+      // remove last SEP
+      links.setLength(links.length() - 1);
+    }
+    return links.toString();
+  }
+
+  @Override
+  public List<String> getLinksForMenu()
+  {
+    List<String> links = new ArrayList<String>();
+    for (String key : selectedUrls)
+    {
+      links.add(urls.get(key).toStringWithTarget());
+    }
+    return links;
+  }
+
+  @Override
+  public List<UrlLinkDisplay> getLinksForTable()
+  {
+    return super.getLinksForTable(urls, selectedUrls, false);
+  }
+
+  @Override
+  public void setUrlData(List<UrlLinkDisplay> links)
+  {
+    selectedUrls = new ArrayList<String>();
+
+    Iterator<UrlLinkDisplay> it = links.iterator();
+    while (it.hasNext())
+    {
+      UrlLinkDisplay link = it.next();
+
+      // Handle links with MIRIAM ids only
+      if (isMiriamId(link.getId()))
+      {
+        // select/deselect links accordingly and set default url
+        if (urls.containsKey(link.getId()))
+        {
+          if (link.getIsSelected())
+          {
+            selectedUrls.add(link.getId());
+          }
+          if (link.getIsPrimary())
+          {
+            setPrimaryUrl(link.getId());
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public String getPrimaryUrl(String seqid)
+  {
+    return super.getPrimaryUrl(seqid, urls);
+  }
+
+  @Override
+  public String getPrimaryUrlId()
+  {
+    return primaryUrl;
+  }
+
+  @Override
+  public String getPrimaryTarget(String seqid)
+  {
+    return null;
+  }
+
+  @Override
+  public String choosePrimaryUrl()
+  {
+    return null;
+  }
+
+  @Override
+  public boolean contains(String id)
+  {
+    return (urls.containsKey(id));
+  }
+}
diff --git a/src/jalview/urls/UrlLinkDisplay.java b/src/jalview/urls/UrlLinkDisplay.java
new file mode 100644 (file)
index 0000000..0eabff7
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.urls;
+
+import jalview.util.MessageManager;
+import jalview.util.UrlLink;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * UrlLink table row definition
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+
+public class UrlLinkDisplay
+{
+  private String id; // id is not supplied to display, but used to identify
+                     // entries when saved
+
+  private boolean isPrimary;
+
+  private boolean isSelected;
+
+  private UrlLink link;
+
+  // Headers for columns in table
+  private final static List<String> colNames = new ArrayList<String>()
+  {
+    {
+      add(MessageManager.formatMessage("label.database"));
+      add(MessageManager.formatMessage("label.name"));
+      add(MessageManager.formatMessage("label.url"));
+      add(MessageManager.formatMessage("label.inmenu"));
+      add(MessageManager.formatMessage("label.primary"));
+      add(MessageManager.formatMessage("label.id"));
+    }
+  };
+
+  // column positions
+  public final static int DATABASE = 0;
+
+  public final static int NAME = 1;
+
+  public final static int URL = 2;
+
+  public final static int SELECTED = 3;
+
+  public final static int PRIMARY = 4;
+
+  public final static int ID = 5;
+
+  public UrlLinkDisplay(String rowId, UrlLink rowLink,
+          boolean rowSelected, boolean rowDefault)
+  {
+    id = rowId;
+    isPrimary = rowDefault;
+    isSelected = rowSelected;
+
+    link = rowLink;
+  }
+
+  // getters/setters
+  public String getId()
+  {
+    return id;
+  }
+
+  public String getDescription()
+  {
+    return link.getLabel();
+  }
+
+  public String getDBName()
+  {
+    return link.getTarget();
+  }
+
+  public String getUrl()
+  {
+    return link.getUrlWithToken();
+  }
+
+  public boolean getIsPrimary()
+  {
+    return isPrimary;
+  }
+
+  public boolean getIsSelected()
+  {
+    return isSelected;
+  }
+
+  public void setDBName(String name)
+  {
+    link.setTarget(name);
+  }
+
+  public void setUrl(String rowUrl)
+  {
+    link = new UrlLink(getDescription(), rowUrl, getDBName());
+  }
+
+  public void setDescription(String desc)
+  {
+    link.setLabel(desc);
+  }
+
+  public void setIsDefault(boolean rowDefault)
+  {
+    isPrimary = rowDefault;
+  }
+
+  public void setIsSelected(boolean rowSelected)
+  {
+    isSelected = rowSelected;
+  }
+
+  public Object getValue(int index)
+  {
+    switch (index)
+    {
+    case ID:
+      return id;
+    case URL:
+      return getUrl();
+    case PRIMARY:
+      return isPrimary;
+    case SELECTED:
+      return isSelected;
+    case NAME:
+      return getDescription();
+    case DATABASE:
+      return getDBName();
+    default:
+      return null;
+    }
+  }
+
+  public void setValue(int index, Object value)
+  {
+    switch (index)
+    {
+    case ID:
+      id = (String) value;
+      break;
+    case URL:
+      setUrl((String) value);
+      break;
+    case PRIMARY:
+      isPrimary = (boolean) value;
+      break;
+    case SELECTED:
+      isSelected = (boolean) value;
+      break;
+    case NAME:
+      setDescription((String) value);
+    case DATABASE:
+      setDBName((String) value);
+      break;
+    default:
+      // do nothing
+    }
+  }
+
+  /**
+   * Identify editable columns
+   * 
+   * @param index
+   *          index of column
+   * @return whether column can be edited in table
+   */
+  public boolean isEditable(int index)
+  {
+    if (index == PRIMARY)
+    {
+      // primary link must not be a $DB_ACCESSION$ link
+      // so only allow editing if it is not
+      return (!link.usesDBAccession());
+    }
+    else if (index == SELECTED)
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  /**
+   * Get list of column names to display in UI
+   * 
+   * @return column names
+   */
+  public static List<String> getDisplayColumnNames()
+  {
+    // Display names between DESCRIPTION and ID (excludes ID)
+    return colNames.subList(DATABASE, ID);
+  }
+}
diff --git a/src/jalview/urls/UrlLinkTableModel.java b/src/jalview/urls/UrlLinkTableModel.java
new file mode 100644 (file)
index 0000000..d6d26b5
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.urls;
+
+import jalview.bin.Cache;
+import jalview.urls.api.UrlProviderI;
+import jalview.util.UrlLink;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.RowFilter.Entry;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+
+/**
+ * TableModel for UrlLinks table
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+
+public class UrlLinkTableModel extends AbstractTableModel
+{
+  // local storage of data
+  private List<UrlLinkDisplay> data;
+
+  // supplier of url data
+  private UrlProviderI dataProvider;
+
+  // list of columns to display in table in correct order
+  private List<String> displayColumns;
+
+  // row in table which is currently the primary
+  private int primaryRow;
+
+  /**
+   * UrlLinkTableModel constructor
+   * 
+   * @param baseData
+   *          base data set to be presented in table
+   * @param entryNames
+   *          keys of entries in baseData's nested hashmap. Should match order
+   *          in displayColNames
+   * @param displayColNames
+   *          names of columns to display in order.
+   * @param keyColName
+   *          name of column corresponding to keys in baseData
+   */
+  public UrlLinkTableModel(UrlProviderI baseData)
+  {
+    dataProvider = baseData;
+    data = baseData.getLinksForTable();
+    displayColumns = UrlLinkDisplay.getDisplayColumnNames();
+
+    // find the primary row
+    primaryRow = 0;
+    Iterator<UrlLinkDisplay> it = data.iterator();
+    while (it.hasNext())
+    {
+      if (it.next().getIsPrimary())
+      {
+        break;
+      }
+      else
+      {
+        primaryRow++;
+      }
+    }
+
+    // set up listener which updates data source when table changes
+    this.addTableModelListener(new TableModelListener()
+    {
+      @Override
+      public void tableChanged(TableModelEvent e)
+      {
+        try
+        {
+          // update the UrlProvider from data list
+          dataProvider.setUrlData(data);
+        } catch (IllegalArgumentException ex)
+        {
+          Cache.log.error(ex.getMessage());
+        }
+      }
+    });
+
+  }
+
+  @Override
+  public int getRowCount()
+  {
+    if (data == null)
+    {
+      return 0;
+    }
+    else
+    {
+      return data.size();
+    }
+  }
+
+  @Override
+  public int getColumnCount()
+  {
+    return displayColumns.size();
+  }
+
+  @Override
+  public Object getValueAt(int rowIndex, int columnIndex)
+  {
+    return data.get(rowIndex).getValue(columnIndex);
+  }
+
+  @Override
+  public boolean isCellEditable(int rowIndex, int columnIndex)
+  {
+    return data.get(rowIndex).isEditable(columnIndex);
+  }
+
+  /**
+   * Determine if a row is editable indirectly (rather than directly in table as
+   * in isCellEditable)
+   * 
+   * @param rowIndex
+   * @return true if row can be edited indirectly
+   */
+  public boolean isRowEditable(int rowIndex)
+  {
+    // to edit, row must be a user entered row
+    return (dataProvider.isUserEntry(data.get(rowIndex).getId()));
+  }
+
+  /**
+   * Determine if a row is deletable
+   * 
+   * @param rowIndex
+   *          the row to be tested
+   * @return true if row can be deleted
+   */
+  public boolean isRowDeletable(int rowIndex)
+  {
+    // to delete, row must be a user entered row, and not the default row
+    return (dataProvider.isUserEntry(data.get(rowIndex).getId()) && !data
+            .get(rowIndex).getIsPrimary());
+  }
+
+  @Override
+  public void setValueAt(Object aValue, int rowIndex, int columnIndex)
+  {
+    if (columnIndex == UrlLinkDisplay.PRIMARY)
+    {
+      // Default url column: exactly one row must always be true
+      if (rowIndex != primaryRow)
+      {
+        // selected row is not currently the default
+        // set the current default to false
+        data.get(primaryRow).setValue(columnIndex, false);
+        fireTableRowsUpdated(primaryRow, primaryRow);
+
+        // set the default to be the selected row
+        primaryRow = rowIndex;
+        data.get(rowIndex).setValue(columnIndex, aValue);
+
+        fireTableRowsUpdated(rowIndex, rowIndex);
+      }
+    }
+    else
+    {
+      data.get(rowIndex).setValue(columnIndex, aValue);
+      fireTableRowsUpdated(rowIndex, rowIndex);
+    }
+  }
+
+  @Override
+  public Class<?> getColumnClass(int columnIndex)
+  {
+    return getValueAt(0, columnIndex).getClass();
+  }
+
+  @Override
+  public String getColumnName(int columnIndex)
+  {
+    return displayColumns.get(columnIndex);
+  }
+
+  public void removeRow(int rowIndex)
+  {
+    // remove the row from data
+    data.remove(rowIndex);
+
+    // update default row
+    if (primaryRow > rowIndex)
+    {
+      primaryRow--;
+    }
+
+    // fire update which will update data source
+    fireTableRowsDeleted(rowIndex, rowIndex);
+  }
+
+  public int insertRow(String name, String url)
+  {
+    // add a row to the data
+    UrlLink link = new UrlLink(name, url, name);
+    UrlLinkDisplay u = new UrlLinkDisplay(name, link, true, false);
+    int index = data.size();
+    data.add(u);
+
+    // fire update which will update data source
+    fireTableRowsInserted(index, index);
+    return index;
+  }
+
+  public int getPrimaryColumn()
+  {
+    return UrlLinkDisplay.PRIMARY;
+  }
+
+  public int getNameColumn()
+  {
+    return UrlLinkDisplay.NAME;
+  }
+
+  public int getDatabaseColumn()
+  {
+    return UrlLinkDisplay.DATABASE;
+  }
+
+  public int getIdColumn()
+  {
+    return UrlLinkDisplay.ID;
+  }
+
+  public int getUrlColumn()
+  {
+    return UrlLinkDisplay.URL;
+  }
+
+  public int getSelectedColumn()
+  {
+    return UrlLinkDisplay.SELECTED;
+  }
+
+  public boolean isUserEntry(
+          Entry<? extends TableModel, ? extends Object> entry)
+  {
+    return dataProvider
+            .isUserEntry(entry.getStringValue(UrlLinkDisplay.ID));
+  }
+
+  public boolean isUniqueName(String name)
+  {
+    return !dataProvider.contains(name);
+  }
+}
diff --git a/src/jalview/urls/UrlProvider.java b/src/jalview/urls/UrlProvider.java
new file mode 100644 (file)
index 0000000..bd01e89
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls;
+
+import static jalview.util.UrlConstants.SEP;
+
+import jalview.urls.api.UrlProviderI;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * 
+ * Implements the UrlProviderI interface for a composite UrlProvider object
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public class UrlProvider implements UrlProviderI
+{
+  // List of actual URL link providers
+  private List<UrlProviderI> providers;
+
+  // Specific reference to custom URL link provider
+  private UrlProviderI customProvider;
+
+  /**
+   * Constructor for UrlProvider composite
+   * 
+   * @param defaultUrlString
+   *          id of default url
+   * @param allProviders
+   *          list of UrlProviders this provider gives access to
+   */
+  public UrlProvider(String defaultUrlString,
+          List<UrlProviderI> allProviders)
+  {
+    providers = allProviders;
+
+    customProvider = findCustomProvider();
+
+    // check that the defaultUrl still exists
+    if (!contains(defaultUrlString))
+    {
+      // if the defaultUrl can't be found in any of the providers
+      // set up a custom default url
+      choosePrimaryUrl();
+    }
+    else
+    {
+      setPrimaryUrl(defaultUrlString);
+    }
+  }
+
+  /*
+   * Store ref to custom url provider
+   */
+  private UrlProviderI findCustomProvider()
+  {
+    for (UrlProviderI p : providers)
+    {
+      if (p instanceof CustomUrlProvider)
+      {
+        return p;
+      }
+    }
+
+    System.out
+            .println("Error initialising UrlProvider - no custom url provider");
+    return null;
+  }
+  
+  @Override
+  public boolean setPrimaryUrl(String id)
+  {
+    boolean outcome = false;
+    for (UrlProviderI p : providers)
+    {
+      if (p.setPrimaryUrl(id))
+      {
+        outcome = true;
+      }
+    }
+    if (!outcome)
+    {
+      throw new IllegalArgumentException();
+    }
+    return outcome;
+  }
+
+  @Override
+  public boolean contains(String id)
+  {
+    boolean outcome = false;
+    for (UrlProviderI p : providers)
+    {
+      if (p.contains(id))
+      {
+        outcome = true;
+      }
+    }
+    return outcome;
+  }
+  
+  @Override
+  public String writeUrlsAsString(boolean selected)
+  {
+    String result = "";
+    for (UrlProviderI p : providers)
+    {
+      String next = p.writeUrlsAsString(selected);
+      if (!next.isEmpty())
+      {
+        result += next;
+        result += SEP;
+      }
+    }
+    // remove last sep
+    if (!result.isEmpty())
+    {
+      result = result.substring(0, result.length() - 1);
+    }
+    return result;
+  }
+
+  @Override
+  public Vector<String> getLinksForMenu()
+  {
+    Vector<String> fullLinks = new Vector<String>();
+    for (UrlProviderI p : providers)
+    {
+      List<String> links = p.getLinksForMenu();
+      if (links != null)
+      {
+        // will obliterate links with same keys from different providers
+        // must have checks in place to prevent user from duplicating ids
+        fullLinks.addAll(links);
+      }
+    }
+    return fullLinks;
+  }
+
+  @Override
+  public List<UrlLinkDisplay> getLinksForTable()
+  {
+    ArrayList<UrlLinkDisplay> displayLinks = new ArrayList<UrlLinkDisplay>();
+    for (UrlProviderI p : providers)
+    {
+      displayLinks.addAll(p.getLinksForTable());
+    }
+    return displayLinks;
+  }
+
+  @Override
+  public void setUrlData(List<UrlLinkDisplay> links)
+  {
+    for (UrlProviderI p : providers)
+    {
+      p.setUrlData(links);
+    }
+  }
+
+  @Override
+  public String getPrimaryUrl(String seqid)
+  {
+    String link = null;
+    for (UrlProviderI p : providers)
+    {
+      if (p.getPrimaryUrl(seqid) == null)
+      {
+        continue;
+      }
+      else
+      {
+        link = p.getPrimaryUrl(seqid);
+        break;
+      }
+    }
+    return link;
+  }
+
+  @Override
+  public String getPrimaryUrlId()
+  {
+    String id = null;
+    for (UrlProviderI p : providers)
+    {
+      if (p.getPrimaryUrlId() == null)
+      {
+        continue;
+      }
+      else
+      {
+        id = p.getPrimaryUrlId();
+        break;
+      }
+    }
+    return id;
+  }
+
+  @Override
+  public String getPrimaryTarget(String seqid)
+  {
+    String target = null;
+    for (UrlProviderI p : providers)
+    {
+      if (p.getPrimaryTarget(seqid) == null)
+      {
+        continue;
+      }
+      else
+      {
+        target = p.getPrimaryTarget(seqid);
+        break;
+      }
+    }
+    return target;
+  }
+  
+  @Override
+  public String choosePrimaryUrl()
+  {
+    // choose a custom url default
+    return customProvider.choosePrimaryUrl();
+  }
+
+  @Override
+  public boolean isUserEntry(String id)
+  {
+    return customProvider.isUserEntry(id);
+  }
+}
diff --git a/src/jalview/urls/UrlProviderImpl.java b/src/jalview/urls/UrlProviderImpl.java
new file mode 100644 (file)
index 0000000..c1a57ca
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls;
+
+import jalview.urls.api.UrlProviderI;
+import jalview.util.UrlLink;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+/**
+ * Leaf node of UrlProvider composite
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+
+public abstract class UrlProviderImpl implements UrlProviderI
+{
+  // minimum length of substitution in url link string
+  protected static final int MIN_SUBST_LENGTH = 4;
+
+  private static final Pattern MIRIAM_PATTERN = Pattern
+          .compile("^MIR:\\d{8}$");
+
+  protected String primaryUrl;
+
+  protected String getPrimaryUrl(String seqid, HashMap<String, UrlLink> urls)
+  {
+    if (seqid.length() < MIN_SUBST_LENGTH)
+    {
+      return null;
+    }
+    else if (primaryUrl == null)
+    {
+      return null;
+    }
+    else if (!urls.containsKey(primaryUrl))
+    {
+      return null;
+    }
+    else
+    {
+      String url = null;
+      UrlLink urlLink = urls.get(primaryUrl);
+      String[] primaryUrls = urlLink.makeUrls(seqid, true);
+      if (primaryUrls == null || primaryUrls[0] == null
+              || primaryUrls[0].length() < MIN_SUBST_LENGTH)
+      {
+        url = null;
+      }
+      else
+      {
+        // just take first URL made from regex
+        url = primaryUrls[1];
+      }
+      return url;
+    }
+  }
+
+  @Override
+  public List<UrlLinkDisplay> getLinksForTable()
+  {
+    return null;
+  }
+
+  protected ArrayList<UrlLinkDisplay> getLinksForTable(
+          HashMap<String, UrlLink> urls, ArrayList<String> selectedUrls,
+          boolean selected)
+  {
+    ArrayList<UrlLinkDisplay> displayLinks = new ArrayList<UrlLinkDisplay>();
+    for (Entry<String, UrlLink> entry : urls.entrySet())
+    {
+      String key = entry.getKey();
+      boolean isPrimary = (key.equals(primaryUrl));
+      boolean isSelected;
+      if (selectedUrls != null)
+      {
+        isSelected = selectedUrls.contains(key);
+      }
+      else
+      {
+        isSelected = selected;
+      }
+      displayLinks.add(new UrlLinkDisplay(key, entry.getValue(),
+              isSelected, isPrimary));
+    }
+    return displayLinks;
+  }
+
+  protected boolean isMiriamId(String id)
+  {
+    return MIRIAM_PATTERN.matcher(id).matches();
+  }
+
+  @Override
+  public boolean isUserEntry(String id)
+  {
+    return !isMiriamId(id);
+  }
+}
+
diff --git a/src/jalview/urls/api/UrlProviderFactoryI.java b/src/jalview/urls/api/UrlProviderFactoryI.java
new file mode 100644 (file)
index 0000000..b96113e
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls.api;
+
+
+/**
+ * Interface to UrlProvider factories
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public interface UrlProviderFactoryI
+{
+  public UrlProviderI createUrlProvider();
+
+}
diff --git a/src/jalview/urls/api/UrlProviderI.java b/src/jalview/urls/api/UrlProviderI.java
new file mode 100644 (file)
index 0000000..728d9be
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls.api;
+
+import jalview.urls.UrlLinkDisplay;
+
+import java.util.List;
+
+/**
+ * Methods for providing consistent access to up-to-date URLs
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+public interface UrlProviderI
+{
+
+  /**
+   * Get names and urls in the UrlProvider as strings for display
+   * 
+   */
+  List<String> getLinksForMenu();
+
+  /**
+   * Get names and urls as strings for display
+   * 
+   */
+  List<UrlLinkDisplay> getLinksForTable();
+
+  /**
+   * Set names and urls from display settings
+   */
+  void setUrlData(List<UrlLinkDisplay> links);
+
+  /**
+   * Get the link for the primary URL
+   * 
+   * @seqid sequence id for which to build link
+   * @return link for the primary URL
+   */
+  String getPrimaryUrl(String seqid);
+
+  /**
+   * Get the primary URL id
+   * 
+   * @return id for primary URL
+   */
+  String getPrimaryUrlId();
+
+  /**
+   * Get the target of the link for the primary URL
+   * 
+   * @seqid sequence id for which to build link
+   * @return target of link for the primary URL
+   */
+  String getPrimaryTarget(String seqid);
+
+  /**
+   * Set the primary URL: if only one URL can be used, this URL is the one which
+   * should be chosen, e.g. provides the URL to be used on double-click of a
+   * sequence id
+   * 
+   * @param id
+   *          the id of the URL to set as primary
+   * @return true if setting is successful
+   * @throws IllegalArgumentException
+   *           if id does not exist as a url in the UrlProvider
+   */
+  boolean setPrimaryUrl(String id) throws IllegalArgumentException;
+
+  /**
+   * Test if UrlProvider contains a url
+   * 
+   * @param id
+   *          to test for
+   * @return true of UrlProvider contains this id, false otherwise
+   */
+  boolean contains(String id);
+
+  /**
+   * Write out all URLs as a string suitable for serialising
+   * 
+   * @return string representation of available URLs
+   */
+  String writeUrlsAsString(boolean selected);
+
+  /**
+   * Choose the primary URL in the event of the selected primary being
+   * unavailable
+   * 
+   * @return id of chosen primary url
+   */
+  String choosePrimaryUrl();
+
+  /**
+   * Determine if id is for a user-defined URL
+   */
+  boolean isUserEntry(String id);
+}
diff --git a/src/jalview/urls/applet/AppletUrlProviderFactory.java b/src/jalview/urls/applet/AppletUrlProviderFactory.java
new file mode 100644 (file)
index 0000000..fcedaa4
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls.applet;
+
+import jalview.urls.CustomUrlProvider;
+import jalview.urls.UrlProvider;
+import jalview.urls.api.UrlProviderFactoryI;
+import jalview.urls.api.UrlProviderI;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * UrlProvider factory for applet code
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+
+public class AppletUrlProviderFactory implements UrlProviderFactoryI
+{
+  private String provDefaultUrl;
+
+  private Map<String, String> provUrlList;
+
+  public AppletUrlProviderFactory(String defaultUrlString,
+          Map<String, String> urlList)
+  {
+    provDefaultUrl = defaultUrlString;
+    provUrlList = urlList;
+  }
+
+  @Override
+  public UrlProviderI createUrlProvider()
+  {
+    // create all the UrlProviders we need
+    List<UrlProviderI> providers = new ArrayList<UrlProviderI>();
+    UrlProviderI customProvider = new CustomUrlProvider(provUrlList, null);
+    providers.add(customProvider);
+
+    UrlProviderI prov = new UrlProvider(provDefaultUrl, providers);
+    return prov;
+  }
+
+}
diff --git a/src/jalview/urls/desktop/DesktopUrlProviderFactory.java b/src/jalview/urls/desktop/DesktopUrlProviderFactory.java
new file mode 100644 (file)
index 0000000..ce933c2
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls.desktop;
+
+import jalview.urls.CustomUrlProvider;
+import jalview.urls.IdentifiersUrlProvider;
+import jalview.urls.UrlProvider;
+import jalview.urls.api.UrlProviderFactoryI;
+import jalview.urls.api.UrlProviderI;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * UrlProvider factory for desktop code
+ * 
+ * @author $author$
+ * @version $Revision$
+ */
+
+public class DesktopUrlProviderFactory implements UrlProviderFactoryI
+{
+
+  private String provDefaultUrl;
+
+  private String menuUrlList;
+
+  private String nonMenuUrlList;
+
+  public DesktopUrlProviderFactory(String defaultUrlString,
+          String cachedUrlList, String userUrlList)
+  {
+    provDefaultUrl = defaultUrlString;
+    menuUrlList = cachedUrlList;
+    nonMenuUrlList = userUrlList;
+  }
+
+  @Override
+  public UrlProviderI createUrlProvider()
+  {
+    // create all the UrlProviders we need
+    List<UrlProviderI> providers = new ArrayList<UrlProviderI>();
+
+    UrlProviderI idProvider = new IdentifiersUrlProvider(menuUrlList);
+    UrlProviderI customProvider = new CustomUrlProvider(menuUrlList,
+            nonMenuUrlList);
+    providers.add(idProvider);
+    providers.add(customProvider);
+
+    return new UrlProvider(provDefaultUrl, providers);
+  }
+
+}
index 1910bff..3347cc7 100644 (file)
@@ -37,14 +37,25 @@ public class UrlConstants
   public static final String SEQUENCE_ID = "SEQUENCE_ID";
 
   /*
-   * Default sequence URL link string for EMBL-EBI search
+   * Separator character used in Url links
+   */
+  public static final String SEP = "|";
+
+  /*
+   * Delimiter character used in Url links
    */
-  public static final String EMBLEBI_STRING = "EMBL-EBI Search|http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$SEQUENCE_ID$";
+  public static final String DELIM = "$";
 
   /*
-   * Default sequence URL link string for SRS 
+   * Default sequence URL link label for EMBL-EBI search
+   */
+  public static final String DEFAULT_LABEL = "EMBL-EBI Search";
+
+  /*
+   * Default sequence URL link string for EMBL-EBI search
    */
-  public static final String SRS_STRING = "SRS|http://srs.ebi.ac.uk/srsbin/cgi-bin/wgetz?-newId+(([uniprot-all:$SEQUENCE_ID$]))+-view+SwissEntry";
+  public static final String DEFAULT_STRING = DEFAULT_LABEL
+          + "|http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$SEQUENCE_ID$";
 
   /*
    * not instantiable
index 3ee6432..0529c73 100644 (file)
@@ -21,6 +21,8 @@
 package jalview.util;
 
 import static jalview.util.UrlConstants.DB_ACCESSION;
+import static jalview.util.UrlConstants.DELIM;
+import static jalview.util.UrlConstants.SEP;
 import static jalview.util.UrlConstants.SEQUENCE_ID;
 
 import jalview.datamodel.DBRefEntry;
@@ -43,10 +45,9 @@ public class UrlLink
    * documentation todo.
    */
 
-  // Internal constants
-  private static final String SEP = "|";
+  private static final String EQUALS = "=";
 
-  private static final String DELIM = "$";
+  private static final String SPACE = " ";
 
   private String urlSuffix;
 
@@ -56,6 +57,8 @@ public class UrlLink
 
   private String label;
 
+  private String dbname;
+
   private String regexReplace;
 
   private boolean dynamic = false;
@@ -81,23 +84,38 @@ public class UrlLink
       dynamic = true;
       usesDBaccession = true;
 
-      sep = parseTargetAndLabel(sep, psqid, link);
+      sep = parseLabel(sep, psqid, link);
 
-      parseUrl(link, DB_ACCESSION, psqid, sep);
+      int endOfRegex = parseUrl(link, DB_ACCESSION, psqid, sep);
+      parseTarget(link, sep, endOfRegex);
     }
     else if (nsqid > -1)
     {
       dynamic = true;
-      sep = parseTargetAndLabel(sep, nsqid, link);
+      sep = parseLabel(sep, nsqid, link);
+
+      int endOfRegex = parseUrl(link, SEQUENCE_ID, nsqid, sep);
 
-      parseUrl(link, SEQUENCE_ID, nsqid, sep);
+      parseTarget(link, sep, endOfRegex);
     }
     else
     {
-      target = link.substring(0, sep);
-      sep = link.lastIndexOf(SEP);
-      label = link.substring(0, sep);
-      urlPrefix = link.substring(sep + 1).trim();
+      label = link.substring(0, sep).trim();
+
+      // if there's a third element in the url link string
+      // it is the target name, otherwise target=label
+      int lastsep = link.lastIndexOf(SEP);
+      if (lastsep != sep)
+      {
+        urlPrefix = link.substring(sep + 1, lastsep).trim();
+        target = link.substring(lastsep + 1).trim();
+      }
+      else
+      {
+        urlPrefix = link.substring(sep + 1).trim();
+        target = label;
+      }
+
       regexReplace = null; // implies we trim any prefix if necessary //
       urlSuffix = null;
     }
@@ -107,9 +125,24 @@ public class UrlLink
   }
 
   /**
+   * Alternative constructor for separate name, link and description
+   * 
+   * @param name
+   *          The string used to match the link to a DB reference id
+   * @param url
+   *          The url to link to
+   * @param desc
+   *          The description of the associated target DB
+   */
+  public UrlLink(String name, String url, String desc)
+  {
+    this(name + SEP + url + SEP + desc);
+  }
+
+  /**
    * @return the url_suffix
    */
-  public String getUrl_suffix()
+  public String getUrlSuffix()
   {
     return urlSuffix;
   }
@@ -117,7 +150,7 @@ public class UrlLink
   /**
    * @return the url_prefix
    */
-  public String getUrl_prefix()
+  public String getUrlPrefix()
   {
     return urlPrefix;
   }
@@ -138,6 +171,16 @@ public class UrlLink
     return label;
   }
 
+  public String getUrlWithToken()
+  {
+    String var = (usesDBaccession ? DB_ACCESSION : SEQUENCE_ID);
+
+    return urlPrefix
+            + (dynamic ? (DELIM + var + ((regexReplace != null) ? EQUALS
+                    + regexReplace + EQUALS + DELIM : DELIM)) : "")
+            + ((urlSuffix == null) ? "" : urlSuffix);
+  }
+
   /**
    * @return the regexReplace
    */
@@ -194,6 +237,16 @@ public class UrlLink
   }
 
   /**
+   * Set the target
+   * 
+   * @param desc
+   */
+  public void setTarget(String desc)
+  {
+    target = desc;
+  }
+
+  /**
    * return one or more URL strings by applying regex to the given idstring
    * 
    * @param idstring
@@ -234,7 +287,7 @@ public class UrlLink
                       + rg.stringMatched(s) + "'");
             }
             // try to collate subgroup matches
-            Vector subs = new Vector();
+            Vector<String> subs = new Vector<String>();
             // have to loop through submatches, collating them at top level
             // match
             int s = 0; // 1;
@@ -278,7 +331,7 @@ public class UrlLink
             String[] res = new String[subs.size()];
             for (int r = 0, rs = subs.size(); r < rs; r++)
             {
-              res[r] = (String) subs.elementAt(r);
+              res[r] = subs.elementAt(r);
             }
             subs.removeAllElements();
             return res;
@@ -307,17 +360,19 @@ public class UrlLink
   @Override
   public String toString()
   {
-    String var = (usesDBaccession ? DB_ACCESSION : SEQUENCE_ID);
+    return label + SEP + getUrlWithToken();
+  }
 
-    return label
-            + SEP
-            + urlPrefix
-            + (dynamic ? (DELIM + var + ((regexReplace != null) ? "="
-                    + regexReplace + "=" + DELIM : DELIM)) : "")
-            + ((urlSuffix == null) ? "" : urlSuffix);
+  /**
+   * @return delimited string containing label, url and target
+   */
+  public String toStringWithTarget()
+  {
+    return label + SEP + getUrlWithToken() + SEP + target;
   }
 
   /**
+   * Parse the label from the link string
    * 
    * @param firstSep
    *          Location of first occurrence of separator in link string
@@ -327,7 +382,7 @@ public class UrlLink
    *          Link string containing database name and url
    * @return Position of last separator symbol prior to any regex symbols
    */
-  protected int parseTargetAndLabel(int firstSep, int psqid, String link)
+  protected int parseLabel(int firstSep, int psqid, String link)
   {
     int p = firstSep;
     int sep = firstSep;
@@ -339,21 +394,44 @@ public class UrlLink
     // Assuming that the URL itself does not contain any SEP symbols
     // sep now contains last pipe symbol position prior to any regex symbols
     label = link.substring(0, sep);
-    if (label.indexOf(SEP) > -1)
-    {
-      // SEP terminated database name / www target at start of Label
-      target = label.substring(0, label.indexOf(SEP));
-    }
-    else if (label.indexOf(" ") > 2)
+
+    return sep;
+  }
+
+  /**
+   * Parse the target from the link string
+   * 
+   * @param link
+   *          Link string containing database name and url
+   * @param sep
+   *          Location of first separator symbol
+   * @param endOfRegex
+   *          Location of end of any regular expression in link string
+   */
+  protected void parseTarget(String link, int sep, int endOfRegex)
+  {
+    int lastsep = link.lastIndexOf(SEP);
+
+    if ((lastsep != sep) && (lastsep > endOfRegex))
     {
-      // space separated Label - matches database name
-      target = label.substring(0, label.indexOf(" "));
+      // final element in link string is the target
+      target = link.substring(lastsep + 1).trim();
     }
     else
     {
       target = label;
     }
-    return sep;
+
+    if (target.indexOf(SEP) > -1)
+    {
+      // SEP terminated database name / www target at start of Label
+      target = target.substring(0, target.indexOf(SEP));
+    }
+    else if (target.indexOf(SPACE) > 2)
+    {
+      // space separated label - first word matches database name
+      target = target.substring(0, target.indexOf(SPACE));
+    }
   }
 
   /**
@@ -367,8 +445,9 @@ public class UrlLink
    *          Position of id or name in link string
    * @param sep
    *          Position of separator in link string
+   * @return Location of end of any regex in link string
    */
-  protected void parseUrl(String link, String varName, int sqidPos, int sep)
+  protected int parseUrl(String link, String varName, int sqidPos, int sep)
   {
     urlPrefix = link.substring(sep + 1, sqidPos).trim();
 
@@ -411,7 +490,14 @@ public class UrlLink
       // verify format is really correct.
       if (link.indexOf(DELIM + varName + DELIM) == sqidPos)
       {
-        urlSuffix = link.substring(sqidPos + startLength - 1);
+        int lastsep = link.lastIndexOf(SEP);
+        if (lastsep < sqidPos + startLength - 1)
+        {
+          // the last SEP character was before the regex, ignore
+          lastsep = link.length();
+        }
+        urlSuffix = link.substring(sqidPos + startLength - 1, lastsep)
+                .trim();
         regexReplace = null;
       }
       else
@@ -420,6 +506,8 @@ public class UrlLink
                 + link;
       }
     }
+
+    return p;
   }
 
   /**
@@ -453,11 +541,11 @@ public class UrlLink
    */
   protected void createStaticLink(Map<String, List<String>> linkset)
   {
-    if (!linkset.containsKey(label + SEP + getUrl_prefix()))
+    if (!linkset.containsKey(label + SEP + getUrlPrefix()))
     {
       // Add a non-dynamic link
-      linkset.put(label + SEP + getUrl_prefix(),
-              Arrays.asList(target, label, null, getUrl_prefix()));
+      linkset.put(label + SEP + getUrlPrefix(),
+              Arrays.asList(target, label, null, getUrlPrefix()));
     }
   }
 
@@ -539,82 +627,4 @@ public class UrlLink
       }
     }
   }
-
-  private static void testUrls(UrlLink ul, String idstring, String[] urls)
-  {
-
-    if (urls == null)
-    {
-      System.out.println("Created NO urls.");
-    }
-    else
-    {
-      System.out.println("Created " + (urls.length / 2) + " Urls.");
-      for (int uls = 0; uls < urls.length; uls += 2)
-      {
-        System.out.println("URL Replacement text : " + urls[uls]
-                + " : URL : " + urls[uls + 1]);
-      }
-    }
-  }
-
-  public static void main(String argv[])
-  {
-    String[] links = new String[] {
-    /*
-     * "AlinkT|Target|http://foo.foo.soo/",
-     * "myUrl1|http://$SEQUENCE_ID=/[0-9]+/=$.someserver.org/foo",
-     * "myUrl2|http://$SEQUENCE_ID=/(([0-9]+).+([A-Za-z]+))/=$.someserver.org/foo"
-     * ,
-     * "myUrl3|http://$SEQUENCE_ID=/([0-9]+).+([A-Za-z]+)/=$.someserver.org/foo"
-     * , "myUrl4|target|http://$SEQUENCE_ID$.someserver.org/foo|too",
-     * "PF1|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(?:PFAM:)?(.+)/=$"
-     * ,
-     * "PF2|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(PFAM:)?(.+)/=$"
-     * ,
-     * "PF3|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/PFAM:(.+)/=$"
-     * , "NOTFER|http://notfer.org/$SEQUENCE_ID=/(?<!\\s)(.+)/=$",
-     */
-    "NESTED|http://nested/$" + DB_ACCESSION
-            + "=/^(?:Label:)?(?:(?:gi\\|(\\d+))|([^:]+))/=$/nested" };
-    String[] idstrings = new String[] {
-    /*
-     * //"LGUL_human", //"QWIQW_123123", "uniprot|why_do+_12313_foo",
-     * //"123123312", "123123 ABCDE foo", "PFAM:PF23943",
-     */
-    "Label:gi|9234|pdb|102L|A" };
-    // TODO: test the setLabel method.
-    for (int i = 0; i < links.length; i++)
-    {
-      UrlLink ul = new UrlLink(links[i]);
-      if (ul.isValid())
-      {
-        System.out.println("\n\n\n");
-        System.out.println("Link " + i + " " + links[i] + " : "
-                + ul.toString());
-        System.out.println(" pref : "
-                + ul.getUrl_prefix()
-                + "\n suf : "
-                + ul.getUrl_suffix()
-                + "\n : "
-                + ((ul.getRegexReplace() != null) ? ul.getRegexReplace()
-                        : ""));
-        for (int ids = 0; ids < idstrings.length; ids++)
-        {
-          System.out.println("ID String : " + idstrings[ids]
-                  + "\nWithout onlyIfMatches:");
-          String[] urls = ul.makeUrls(idstrings[ids], false);
-          testUrls(ul, idstrings[ids], urls);
-          System.out.println("With onlyIfMatches set.");
-          urls = ul.makeUrls(idstrings[ids], true);
-          testUrls(ul, idstrings[ids], urls);
-        }
-      }
-      else
-      {
-        System.err.println("Invalid URLLink : " + links[i] + " : "
-                + ul.getInvalidMessage());
-      }
-    }
-  }
 }
diff --git a/src/jalview/ws/utils/UrlDownloadClient.java b/src/jalview/ws/utils/UrlDownloadClient.java
new file mode 100644 (file)
index 0000000..86e3f76
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.ws.utils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+public class UrlDownloadClient
+{
+  public UrlDownloadClient()
+  {
+
+  }
+
+  /**
+   * Download and save a file from a URL
+   * 
+   * @param urlstring
+   *          url to download from, as string
+   * @param outfile
+   *          the name of file to save the URLs to
+   * @throws IOException
+   */
+  public void download(String urlstring, String outfile) throws IOException
+  {
+    FileOutputStream fos = null;
+    ReadableByteChannel rbc = null;
+    Path temp = null;
+    try
+    {
+      temp = Files.createTempFile(".jalview_", ".tmp");
+
+      URL url = new URL(urlstring);
+      rbc = Channels.newChannel(url.openStream());
+      fos = new FileOutputStream(temp.toString());
+      fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+
+      // copy tempfile to outfile once our download completes
+      // incase something goes wrong
+      Files.copy(temp, Paths.get(outfile),
+              StandardCopyOption.REPLACE_EXISTING);
+    } catch (IOException e)
+    {
+      throw e;
+    } finally
+    {
+      try
+      {
+        if (fos != null)
+        {
+          fos.close();
+        }
+      } catch (IOException e)
+      {
+        System.out
+                .println("Exception while closing download file output stream: "
+                        + e.getMessage());
+      }
+      try
+      {
+        if (rbc != null)
+        {
+          rbc.close();
+        }
+      } catch (IOException e)
+      {
+        System.out.println("Exception while closing download channel: "
+                        + e.getMessage());
+      }
+      try
+      {
+        if (temp != null)
+        {
+          Files.deleteIfExists(temp);
+        }
+      } catch (IOException e)
+      {
+        System.out.println("Exception while deleting download temp file: "
+                        + e.getMessage());
+      }
+    }
+  }
+}
diff --git a/test/jalview/urls/AppletUrlProviderFactoryTest.java b/test/jalview/urls/AppletUrlProviderFactoryTest.java
new file mode 100644 (file)
index 0000000..2a967e4
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls;
+
+import jalview.urls.api.UrlProviderFactoryI;
+import jalview.urls.api.UrlProviderI;
+import jalview.urls.applet.AppletUrlProviderFactory;
+import jalview.util.UrlConstants;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class AppletUrlProviderFactoryTest {
+
+  @Test(groups = { "Functional" })
+  public void testCreateUrlProvider()
+  {
+    final String defaultUrl = UrlConstants.DEFAULT_STRING.substring(
+            UrlConstants.DEFAULT_STRING.indexOf(UrlConstants.SEP) + 1,
+            UrlConstants.DEFAULT_STRING.length());
+    Map<String, String> urlList = new HashMap<String, String>()
+    {
+      {
+        put("Test1", "http://identifiers.org/uniprot/$DB_ACCESSION$");
+        put("Test2", defaultUrl);
+      }
+    };
+
+    UrlProviderFactoryI factory = new AppletUrlProviderFactory("Test2",
+            urlList);
+    UrlProviderI prov = factory.createUrlProvider();
+
+    // default url correctly set
+    Assert.assertEquals(prov.getPrimaryUrlId(), "Test2");
+    Assert.assertEquals(prov.getPrimaryUrl("FER_CAPAN"),
+            defaultUrl.replace("$SEQUENCE_ID$",
+                    "FER_CAPAN"));
+
+    List<UrlLinkDisplay> allLinks = prov.getLinksForTable();
+
+    // 2 links in provider
+    Assert.assertEquals(allLinks.size(), 2);
+
+    // first link set correctly
+    Assert.assertEquals(allLinks.get(0).getId(), "Test1");
+    Assert.assertEquals(allLinks.get(0).getDescription(), "Test1");
+    Assert.assertEquals(allLinks.get(0).getUrl(),
+            "http://identifiers.org/uniprot/$DB_ACCESSION$");
+    Assert.assertFalse(allLinks.get(0).getIsPrimary());
+    Assert.assertTrue(allLinks.get(0).getIsSelected());
+
+    // second link set correctly
+    Assert.assertEquals(allLinks.get(1).getId(), "Test2");
+    Assert.assertEquals(allLinks.get(1).getDescription(), "Test2");
+    Assert.assertEquals(allLinks.get(1).getUrl(), defaultUrl);
+    Assert.assertTrue(allLinks.get(1).getIsPrimary());
+    Assert.assertTrue(allLinks.get(1).getIsSelected());
+  }
+}
diff --git a/test/jalview/urls/CustomUrlProviderTest.java b/test/jalview/urls/CustomUrlProviderTest.java
new file mode 100644 (file)
index 0000000..f9ed893
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+import jalview.urls.api.UrlProviderI;
+import jalview.util.UrlConstants;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Vector;
+
+import org.testng.annotations.Test;
+
+public class CustomUrlProviderTest
+{
+
+  private static final String cachedList = "TEST|http://someurl.blah/$DB_ACCESSION$|"
+          + "ANOTHER|http://test/t$SEQUENCE_ID$|"
+          + "TEST2|http://address/$SEQUENCE_ID$|SRS|"
+          + "http://theSRSlink/$SEQUENCE_ID$";
+
+  private static final String unselectedList = "NON1|http://x/y/$DB_ACCESSION$|"
+          + "NON2|http://a/b/c/$DB_ACCESSION";
+
+  private static final HashMap<String, String> urlMap = new HashMap<String, String>()
+  {
+    {
+      put("TEST","http://someurl.blah/$DB_ACCESSION$");
+      put("ANOTHER","http://test/t$SEQUENCE_ID$");
+      put("TEST2", "http://address/$SEQUENCE_ID$");
+      put("SRS", "http://theSRSlink/$SEQUENCE_ID$");
+    }
+  };
+  
+  private static final HashMap<String, String> unselUrlMap = new HashMap<String, String>()
+  {
+    {
+      put("NON1", "http://x/y/$DB_ACCESSION$");
+      put("NON2", "http://a/b/c/$DB_ACCESSION");
+    }
+  };
+
+  private static final String[] dlinks = {
+      "TEST|http://someurl.blah/$DB_ACCESSION$",
+      "ANOTHER|http://test/t$SEQUENCE_ID$",
+      "TEST2|http://address/$SEQUENCE_ID$",
+ UrlConstants.DEFAULT_STRING };
+
+  private static final String[] unselDlinks = {
+      "NON1|http://x/y/$DB_ACCESSION$", "NON2|http://a/b/c/$DB_ACCESSION" };
+
+  private static final Vector<String> displayLinks = new Vector<String>(
+          Arrays.asList(dlinks));
+
+  private static final Vector<String> unselDisplayLinks = new Vector<String>(
+          Arrays.asList(unselDlinks));
+
+  private static final String[] dlinks2 = { "a|http://x.y.z/$SEQUENCE_ID$" };
+
+  private static final Vector<String> displayLinks2 = new Vector<String>(
+          Arrays.asList(dlinks2));
+
+  private static final String[] list1 = { "a" };
+
+  private static final String[] list2 = { "http://x.y.z/$SEQUENCE_ID$" };
+
+  private static final Vector<String> names = new Vector<String>(
+          Arrays.asList(list1));
+
+  private static final Vector<String> urls = new Vector<String>(
+          Arrays.asList(list2));
+
+  /*
+   * Test default url is set and returned correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testDefaultUrl()
+  {
+    UrlProviderI customProv = new CustomUrlProvider(cachedList,
+            unselectedList);
+    
+    // default url can be set
+    assertTrue(customProv.setPrimaryUrl("ANOTHER"));
+
+    // supplied replacement id must be more than 4 chars
+    String result = customProv.getPrimaryUrl("123");
+    assertEquals(null, result);
+
+    // default url can be retrieved given a sequence id
+    result = customProv.getPrimaryUrl("seqid");
+    assertEquals("http://test/tseqid", result);
+
+    // if there is no default url it sets the default to null
+    assertFalse(customProv.setPrimaryUrl("No default"));
+    result = customProv.getPrimaryUrl("testid");
+    assertEquals(null, result);
+    
+    // choosing the default picks the DEFAULT_STRING option
+    customProv.choosePrimaryUrl();
+    result = customProv.getPrimaryUrl("seqid");
+    assertEquals(
+            UrlConstants.DEFAULT_STRING.split("\\|")[1].split("\\$")[0]
+            + "seqid",
+            result);
+  }
+
+  /*
+   * Test urls are set and returned correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testUrlLinks()
+  {
+    // creation from cached url list works + old links upgraded
+    UrlProviderI customProv = new CustomUrlProvider(cachedList,
+            unselectedList);
+    assertTrue(displayLinks.containsAll(customProv.getLinksForMenu()));
+
+    // creation from map works + old links upgraded
+    UrlProviderI customProv2 = new CustomUrlProvider(urlMap, unselUrlMap);
+    assertTrue(displayLinks.containsAll(customProv2.getLinksForMenu()));
+
+    // writing url links as a string works
+    // because UrlProvider does not guarantee order of links, we can't just
+    // compare the output of writeUrlsAsString to a string, hence the hoops here
+    String result = customProv.writeUrlsAsString(true);
+    UrlProviderI up = new CustomUrlProvider(result, "");
+    assertTrue(displayLinks.containsAll(up.getLinksForMenu()));
+
+    result = customProv.writeUrlsAsString(false);
+    up = new CustomUrlProvider("", result);
+    assertTrue(unselDisplayLinks.containsAll(up.getLinksForMenu()));
+
+    result = customProv2.writeUrlsAsString(true);
+    UrlProviderI up2 = new CustomUrlProvider(result, "");
+    assertTrue(displayLinks.containsAll(up2.getLinksForMenu()));
+
+    result = customProv2.writeUrlsAsString(false);
+    up2 = new CustomUrlProvider("", result);
+    assertTrue(displayLinks.containsAll(up2.getLinksForMenu()));
+  }
+}
diff --git a/test/jalview/urls/DesktopUrlProviderFactoryTest.java b/test/jalview/urls/DesktopUrlProviderFactoryTest.java
new file mode 100644 (file)
index 0000000..cc55034
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.urls;
+
+import jalview.urls.api.UrlProviderI;
+import jalview.urls.desktop.DesktopUrlProviderFactory;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class DesktopUrlProviderFactoryTest
+{
+  private static final String testIdOrgString = "{\"Local\": [{\"id\":\"MIR:00000002\",\"name\":\"ChEBI\",\"pattern\":\"^CHEBI:\\d+$\","
+          + "\"definition\":\"Chemical Entities of Biological Interest (ChEBI)\",\"prefix\":\"chebi\","
+          + "\"url\":\"http://identifiers.org/chebi\"},{\"id\":\"MIR:00000005\",\"name\":\"UniProt Knowledgebase\","
+          + "\"pattern\":\"^([A-N,R-Z][0-9]([A-Z][A-Z, 0-9][A-Z, 0-9][0-9]){1,2})|([O,P,Q][0-9][A-Z, 0-9][A-Z, 0-9][A-Z, 0-9][0-9])(\\.\\d+)?$\","
+          + "\"definition\":\"The UniProt Knowledgebase (UniProtKB)\",\"prefix\":\"uniprot\",\"url\":\"http://identifiers.org/uniprot\"},"
+          + "{\"id\":\"MIR:00000011\",\"name\":\"InterPro\",\"pattern\":\"^IPR\\d{6}$\",\"definition\":\"InterPro\",\"prefix\":\"interpro\","
+          + "\"url\":\"http://identifiers.org/interpro\"},"
+          + "{\"id\":\"MIR:00000372\",\"name\":\"ENA\",\"pattern\":\"^[A-Z]+[0-9]+(\\.\\d+)?$\",\"definition\":\"The European Nucleotide Archive (ENA),\""
+          + "\"prefix\":\"ena.embl\",\"url\":\"http://identifiers.org/ena.embl\"}]}";
+
+  @BeforeMethod(alwaysRun = true)
+  public void setup()
+  {
+    // make a dummy identifiers.org download file
+    File temp = null;
+
+    try
+    {
+      temp = File.createTempFile("tempfile", ".tmp");
+      temp.deleteOnExit();
+      BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
+      bw.write(testIdOrgString);
+      bw.close();
+    } catch (IOException e)
+    {
+      System.out
+              .println("Error initialising DesktopUrlProviderFactoryTest test: "
+                      + e.getMessage());
+    }
+
+    IdOrgSettings.setDownloadLocation(temp.getPath());
+  }
+
+  @Test(groups = { "Functional" })
+  public void testCreateUrlProvider()
+  {
+    String defaultUrlString = "Test1";
+    String defaultUrl = "http://blah.blah/$SEQUENCE_ID$";
+    String cachedUrlList = "MIR:00000005|MIR:00000011|Test1|http://blah.blah/$SEQUENCE_ID$|"
+            + "Test2|http://test2/$DB_ACCESSION$|Test3|http://test3/$SEQUENCE_ID$";
+    String userUrlList = "MIR:00000372|Test4|httpL//another.url/$SEQUENCE_ID$";
+
+    DesktopUrlProviderFactory factory = new DesktopUrlProviderFactory(
+            defaultUrlString, cachedUrlList, userUrlList);
+    UrlProviderI prov = factory.createUrlProvider();
+
+    // default url correctly set
+    Assert.assertEquals(prov.getPrimaryUrlId(), "Test1");
+    Assert.assertEquals(prov.getPrimaryUrl("FER_CAPAN"),
+            defaultUrl.replace("$SEQUENCE_ID$", "FER_CAPAN"));
+
+    List<String> menulinks = prov.getLinksForMenu();
+    List<UrlLinkDisplay> allLinks = prov.getLinksForTable();
+
+    // 8 links in provider - 4 from id file, 4 custom links
+    Assert.assertEquals(allLinks.size(), 8);
+
+    // 5 links in menu (cachedUrlList)
+    Assert.assertEquals(menulinks.size(), 5);
+
+    Assert.assertTrue(menulinks
+            .contains("Test1|http://blah.blah/$SEQUENCE_ID$"));
+    Assert.assertTrue(menulinks
+            .contains("Test2|http://test2/$DB_ACCESSION$"));
+    Assert.assertTrue(menulinks
+            .contains("Test3|http://test3/$SEQUENCE_ID$"));
+    Assert.assertTrue(menulinks
+            .contains("UniProt Knowledgebase|http://identifiers.org/uniprot/$DB_ACCESSION$|uniprot"));
+    Assert.assertTrue(menulinks
+            .contains("InterPro|http://identifiers.org/interpro/$DB_ACCESSION$|interpro"));
+  }
+}
diff --git a/test/jalview/urls/IdentifiersUrlProviderTest.java b/test/jalview/urls/IdentifiersUrlProviderTest.java
new file mode 100644 (file)
index 0000000..eed58b0
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.urls;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+import jalview.urls.api.UrlProviderI;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Vector;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class IdentifiersUrlProviderTest
+{
+  private static final String testIdOrgFile = "{\"Local\": [{\"id\":\"MIR:00000002\",\"name\":\"ChEBI\",\"pattern\":\"^CHEBI:\\d+$\","
+          + "\"definition\":\"Chemical Entities of Biological Interest (ChEBI)\",\"prefix\":\"chebi\","
+          + "\"url\":\"http://identifiers.org/chebi\"},{\"id\":\"MIR:00000005\",\"name\":\"UniProt Knowledgebase\","
+          + "\"pattern\":\"^([A-N,R-Z][0-9]([A-Z][A-Z, 0-9][A-Z, 0-9][0-9]){1,2})|([O,P,Q][0-9][A-Z, 0-9][A-Z, 0-9][A-Z, 0-9][0-9])(\\.\\d+)?$\","
+          + "\"definition\":\"The UniProt Knowledgebase (UniProtKB)\",\"prefix\":\"uniprot\",\"url\":\"http://identifiers.org/uniprot\"},"
+          + "{\"id\":\"MIR:00000011\",\"name\":\"InterPro\",\"pattern\":\"^IPR\\d{6}$\",\"definition\":\"InterPro\",\"prefix\":\"interpro\","
+          + "\"url\":\"http://identifiers.org/interpro\"},"
+          + "{\"id\":\"MIR:00000372\",\"name\":\"ENA\",\"pattern\":\"^[A-Z]+[0-9]+(\\.\\d+)?$\",\"definition\":\"The European Nucleotide Archive (ENA),\""
+          + "\"prefix\":\"ena.embl\",\"url\":\"http://identifiers.org/ena.embl\"}]}";
+  
+  private static final String[] dlinks = {
+      "UniProt Knowledgebase|http://identifiers.org/uniprot/$DB_ACCESSION$|uniprot",
+      "InterPro|http://identifiers.org/interpro/$DB_ACCESSION$|interpro",
+      "ENA|http://identifiers.org/ena.embl/$DB_ACCESSION$|ena.embl" };
+
+  private static final String[] dlinks1 = {
+      "MIR:00000011|http://identifiers.org/interpro/$DB_ACCESSION$",
+      "MIR:00000372|http://identifiers.org/ena.embl/$DB_ACCESSION$" };
+
+  private static final String[] dlinks2 = {
+      "MIR:00000005|http://identifiers.org/uniprot/$DB_ACCESSION$",
+      "MIR:00000011|http://identifiers.org/interpro/$DB_ACCESSION$" };
+
+  private static final String stringLinks = "MIR:00000005|http://identifiers.org/uniprot/$DB_ACCESSION$"
+          + "MIR:00000011|http://identifiers.org/interpro/$DB_ACCESSION$"
+          + "MIR:00000372|http://identifiers.org/ena.embl/$DB_ACCESSION$";
+
+  private static final String[] unselDlinks = { "ChEBI|http://identifiers.org/chebi/$DB_ACCESSION$" };
+
+  private static final Vector<String> displayLinks = new Vector<String>(
+        Arrays.asList(dlinks));
+  
+  private static final Vector<String> unselDisplayLinks = new Vector<String>(
+          Arrays.asList(unselDlinks));
+
+  private static final Vector<String> displayLinks1 = new Vector<String>(
+          Arrays.asList(dlinks1));
+
+  private static final Vector<String> displayLinks2 = new Vector<String>(
+          Arrays.asList(dlinks2));
+
+  private static final HashMap<String, String> urlMap = new HashMap<String, String>()
+  {
+    {
+      put("MIR:00000005", "http://identifiers.org/uniprot/$DB_ACCESSION$");
+      put("MIR:00000011", "http://identifiers.org/interpro/$DB_ACCESSION$");
+      put("MIR:00000372", "http://identifiers.org/ena.embl/$DB_ACCESSION$");
+    }
+  };
+
+  private String testfile = "";
+
+
+  @BeforeClass(alwaysRun = true)
+  public void setup()
+  {
+    // setup test ids in a file
+    File outFile = null;
+    try
+    {
+      outFile = File.createTempFile("testidsfile", "txt");
+      outFile.deleteOnExit();
+
+      FileWriter fw = new FileWriter(outFile);
+      fw.write(testIdOrgFile);
+      fw.close();
+
+      testfile = outFile.getAbsolutePath();
+
+    } catch (Exception ex)
+    {
+      System.err.println(ex);
+    }
+
+    IdOrgSettings.setDownloadLocation(testfile);
+  }
+
+  /*
+   * Test urls are set and returned correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testUrlLinks()
+  {
+    // creation from cached id list
+    String idList = "MIR:00000005|MIR:00000011|MIR:00000372";
+    UrlProviderI idProv = new IdentifiersUrlProvider(idList);
+    
+    assertTrue(displayLinks.containsAll(idProv.getLinksForMenu()));
+
+    // because UrlProvider does not guarantee order of links, we can't just
+    // compare the output of writeUrlsAsString to a string, hence the hoops here
+    String result = idProv.writeUrlsAsString(true);
+    UrlProviderI up = new IdentifiersUrlProvider(result);
+    assertTrue(displayLinks.containsAll(up.getLinksForMenu()));
+
+    result = idProv.writeUrlsAsString(false);
+    up = new IdentifiersUrlProvider(result);
+    assertTrue(unselDisplayLinks.containsAll(up.getLinksForMenu()));
+
+  }
+
+  /*
+   * Test default is set and returned correctly
+   */
+  @Test(groups = { "Functional" })
+  public void testDefaultUrl()
+  {
+    // creation from cached id list
+    String idList = "MIR:00000005|MIR:00000011|MIR:00000372";
+    UrlProviderI idProv = new IdentifiersUrlProvider(idList);
+    
+    // initially no default
+    assertEquals(null, idProv.getPrimaryUrl("seqid"));
+    
+    // set and then retrieve default
+    assertTrue(idProv.setPrimaryUrl("MIR:00000005"));
+    assertEquals("http://identifiers.org/uniprot/seqid",
+            idProv.getPrimaryUrl("seqid"));
+
+    // ids less than length 4 return null
+    assertEquals(null,
+            idProv.getPrimaryUrl("123"));
+
+    // attempt to set bad default
+    assertFalse(idProv.setPrimaryUrl("MIR:00001234"));
+    // default set to null (as default should have been set elsewhere)
+    assertEquals(null, idProv.getPrimaryUrl("seqid"));
+
+    // chooseDefaultUrl not implemented for IdentifiersUrlProvider
+    assertEquals(null, idProv.choosePrimaryUrl());
+  }
+}
diff --git a/test/jalview/urls/UrlLinkDisplayTest.java b/test/jalview/urls/UrlLinkDisplayTest.java
new file mode 100644 (file)
index 0000000..8c50082
--- /dev/null
@@ -0,0 +1,145 @@
+package jalview.urls;
+
+import jalview.util.UrlLink;
+
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class UrlLinkDisplayTest {
+
+  @Test(groups = { "Functional" })
+  public void testDisplayColumnNames()
+  {
+    // 5 column names returned although 6 names internal to UrlLinkDisplay
+    List<String> names = UrlLinkDisplay.getDisplayColumnNames();
+    Assert.assertEquals(names.size(), 5);
+  }
+
+  @Test(groups = { "Functional" })
+  public void getValue()
+  {
+    UrlLink link = new UrlLink("Test Name",
+            "http://identifiers.org/$DB_ACCESSION$", "TestDB");
+    UrlLinkDisplay u = new UrlLinkDisplay("Test", link, false, false);
+
+    Assert.assertFalse((boolean) u.getValue(UrlLinkDisplay.PRIMARY));
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.ID), "Test");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.DATABASE), "TestDB");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.NAME), "Test Name");
+    Assert.assertFalse((boolean) u.getValue(UrlLinkDisplay.SELECTED));
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.URL),
+            "http://identifiers.org/$DB_ACCESSION$");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testIsEditable()
+  {
+    // only default and selected columns are editable ever
+    // default only editable if link contains $SEQUENCE_ID$
+
+    UrlLink link = new UrlLink("Test Url",
+            "http://identifiers.org/$DB_ACCESSION$",
+ "TestName");
+    UrlLinkDisplay u = new UrlLinkDisplay("Test", link, false, false);
+
+    Assert.assertFalse(u.isEditable(UrlLinkDisplay.PRIMARY));
+    Assert.assertTrue(u.isEditable(UrlLinkDisplay.SELECTED));
+    Assert.assertFalse(u.isEditable(UrlLinkDisplay.ID));
+    Assert.assertFalse(u.isEditable(UrlLinkDisplay.URL));
+    Assert.assertFalse(u.isEditable(UrlLinkDisplay.NAME));
+    Assert.assertFalse(u.isEditable(UrlLinkDisplay.DATABASE));
+
+    UrlLink vlink = new UrlLink("Test Sequence ID Url",
+            "http://myurl/$SEQUENCE_ID$", "TestName");
+    UrlLinkDisplay v = new UrlLinkDisplay("Test", vlink, false, false);
+
+    Assert.assertTrue(v.isEditable(UrlLinkDisplay.PRIMARY));
+    Assert.assertTrue(v.isEditable(UrlLinkDisplay.SELECTED));
+    Assert.assertFalse(v.isEditable(UrlLinkDisplay.ID));
+    Assert.assertFalse(v.isEditable(UrlLinkDisplay.URL));
+    Assert.assertFalse(v.isEditable(UrlLinkDisplay.NAME));
+    Assert.assertFalse(v.isEditable(UrlLinkDisplay.DATABASE));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testName()
+  {
+    UrlLink link = new UrlLink("Test Url",
+            "http://identifiers.org/$DB_ACCESSION$", "TestName");
+    UrlLinkDisplay u = new UrlLinkDisplay("Test", link, false, false);
+
+    // Name initially as input in link
+    Assert.assertEquals(u.getDBName(), "TestName");
+
+    // Setting updates name
+    u.setDBName("NewName");
+    Assert.assertEquals(u.getDBName(), "NewName");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testDescription()
+  {
+    UrlLink link = new UrlLink("Test Name",
+            "http://identifiers.org/$DB_ACCESSION$", "TestDB");
+    UrlLinkDisplay u = new UrlLinkDisplay("Test", link, false, false);
+
+    // Desc initially as input in link
+    Assert.assertEquals(u.getDescription(), "Test Name");
+
+    // Setting updates name
+    u.setDescription("New Desc");
+    Assert.assertEquals(u.getDescription(), "New Desc");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testUrl()
+  {
+    UrlLink link = new UrlLink("Test Name",
+            "http://identifiers.org/$DB_ACCESSION$", "TestDB");
+    UrlLinkDisplay u = new UrlLinkDisplay("Test", link, false, false);
+
+    // Url initially as input in link
+    Assert.assertEquals(u.getUrl(), "http://identifiers.org/$DB_ACCESSION$");
+
+    // Setting updates url
+    u.setUrl("http://something.new/$SEQUENCE_ID$");
+    Assert.assertEquals(u.getUrl(), "http://something.new/$SEQUENCE_ID$");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetSetValue()
+  {
+    UrlLink link = new UrlLink("Test Name",
+            "http://identifiers.org/$DB_ACCESSION$", "TestDB");
+    UrlLinkDisplay u = new UrlLinkDisplay("Test", link, false, false);
+
+    Assert.assertFalse((boolean) u.getValue(UrlLinkDisplay.PRIMARY));
+    Assert.assertFalse((boolean) u.getValue(UrlLinkDisplay.SELECTED));
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.DATABASE), "TestDB");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.NAME), "Test Name");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.ID), "Test");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.URL),
+            "http://identifiers.org/$DB_ACCESSION$");
+
+    u.setValue(UrlLinkDisplay.PRIMARY, true);
+    Assert.assertTrue((boolean) u.getValue(UrlLinkDisplay.PRIMARY));
+
+    u.setValue(UrlLinkDisplay.SELECTED, true);
+    Assert.assertTrue((boolean) u.getValue(UrlLinkDisplay.SELECTED));
+
+    u.setValue(UrlLinkDisplay.NAME, "New Desc");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.NAME), "New Desc");
+
+    u.setValue(UrlLinkDisplay.DATABASE, "NewName");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.DATABASE), "NewName");
+
+    u.setValue(UrlLinkDisplay.ID, "New ID");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.ID), "New ID");
+
+    u.setValue(UrlLinkDisplay.URL, "http://something.new/$SEQUENCE_ID$");
+    Assert.assertEquals(u.getValue(UrlLinkDisplay.URL),
+            "http://something.new/$SEQUENCE_ID$");
+  }
+}
diff --git a/test/jalview/urls/UrlLinkTableModelTest.java b/test/jalview/urls/UrlLinkTableModelTest.java
new file mode 100644 (file)
index 0000000..ab190ef
--- /dev/null
@@ -0,0 +1,348 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package jalview.urls;
+
+import static jalview.util.UrlConstants.DELIM;
+import static jalview.util.UrlConstants.SEP;
+import static jalview.util.UrlConstants.SEQUENCE_ID;
+
+import jalview.urls.api.UrlProviderI;
+import jalview.util.MessageManager;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.event.TableModelListener;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class UrlLinkTableModelTest {
+
+  private static final String inmenu = "TEST|http://someurl.blah/$DB_ACCESSION$|"
+          + "ANOTHER|http://test/t$SEQUENCE_ID$|"
+          + "TEST2|http://address/$SEQUENCE_ID$|SRS|"
+          + "http://theSRSlink/$SEQUENCE_ID$|"
+          + "MIR:00000005|MIR:00000011|MIR:00000372";
+
+  private static final String notinmenu = "Not1|http://not.in.menu/$DB_ACCESSION$|"
+          + "Not2|http://not.in.menu.either/$DB_ACCESSION$";
+
+  private static final String testIdOrgString = "{\"Local\": [{\"id\":\"MIR:00000002\",\"name\":\"ChEBI\",\"pattern\":\"^CHEBI:\\d+$\","
+          + "\"definition\":\"Chemical Entities of Biological Interest (ChEBI)\",\"prefix\":\"chebi\","
+          + "\"url\":\"http://identifiers.org/chebi\"},{\"id\":\"MIR:00000005\",\"name\":\"UniProt Knowledgebase\","
+          + "\"pattern\":\"^([A-N,R-Z][0-9]([A-Z][A-Z, 0-9][A-Z, 0-9][0-9]){1,2})|([O,P,Q][0-9][A-Z, 0-9][A-Z, 0-9][A-Z, 0-9][0-9])(\\.\\d+)?$\","
+          + "\"definition\":\"The UniProt Knowledgebase (UniProtKB)\",\"prefix\":\"uniprot\",\"url\":\"http://identifiers.org/uniprot\"},"
+          + "{\"id\":\"MIR:00000011\",\"name\":\"InterPro\",\"pattern\":\"^IPR\\d{6}$\",\"definition\":\"InterPro\",\"prefix\":\"interpro\","
+          + "\"url\":\"http://identifiers.org/interpro\"},"
+          + "{\"id\":\"MIR:00000372\",\"name\":\"ENA\",\"pattern\":\"^[A-Z]+[0-9]+(\\.\\d+)?$\",\"definition\":\"The European Nucleotide Archive (ENA),\""
+          + "\"prefix\":\"ena.embl\",\"url\":\"http://identifiers.org/ena.embl\"}]}";
+
+  private UrlProviderI prov;
+
+  @BeforeMethod(alwaysRun = true)
+  public void setup()
+  {
+    // set up UrlProvider data as the source for the TableModel
+    // the data gets updated by the TableModel, so needs to be reinitialised for
+    // each test
+
+    // make a dummy identifiers.org download file
+    File temp = null;
+    try
+    {
+      temp = File.createTempFile("tempfile", ".tmp");
+      temp.deleteOnExit();
+      BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
+      bw.write(testIdOrgString);
+      bw.close();
+    } catch (IOException e)
+    {
+      System.out.println("Error initialising UrlLinkTableModel test: "
+              + e.getMessage());
+    }
+
+    // set up custom and identifiers.org url providers
+    IdOrgSettings.setDownloadLocation(temp.getPath());
+    IdentifiersUrlProvider idprov = new IdentifiersUrlProvider(inmenu);
+    CustomUrlProvider cprov = new CustomUrlProvider(inmenu, notinmenu);
+    List<UrlProviderI> provlist = new ArrayList<UrlProviderI>();
+    provlist.add(idprov);
+    provlist.add(cprov);
+
+    prov = new UrlProvider("TEST2", provlist);
+  }
+
+  /*
+   * Test that the table model is correctly initialised
+   * Display columns and default row are set; data provider listening event set up
+   */
+  @Test(groups = { "Functional" })
+  public void testInitialisation()
+  {
+    int defaultCol = 4;
+    int dbCol = 0;
+    int descCol = 1;
+
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    // exactly one table model listener
+    TableModelListener[] listeners = m
+            .getListeners(TableModelListener.class);
+    Assert.assertEquals(listeners.length, 1);
+
+    // default row exists, there is exactly 1, and it matches the supplied
+    // default
+    int count = 0;
+    for (int row = 0; row < m.getRowCount(); row++)
+    {
+      boolean isDefault = (boolean) m.getValueAt(row, defaultCol);
+      if (isDefault)
+      {
+        count++;
+        String defaultDBName = (String) m.getValueAt(row, dbCol);
+        Assert.assertEquals(defaultDBName, "TEST2");
+
+        String defaultDesc = (String) m.getValueAt(row, descCol);
+        Assert.assertEquals(defaultDesc, "TEST2");
+      }
+    }
+    Assert.assertEquals(count, 1);
+  }
+
+  /*
+   * Test row and column counts
+   */
+  @Test(groups = { "Functional" })
+  public void testCounts()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    // correct numbers of column and rows
+    Assert.assertEquals(m.getColumnCount(), 5);
+    Assert.assertEquals(m.getRowCount(), 10);
+  }
+
+  /*
+   * Test column access
+   */
+  @Test(groups = { "Functional" })
+  public void testColumns()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    // check column names
+    Assert.assertEquals(m.getColumnName(0),
+            MessageManager.formatMessage("label.database"));
+    Assert.assertEquals(m.getColumnName(1),
+            MessageManager.formatMessage("label.name"));
+    Assert.assertEquals(m.getColumnName(2),
+            MessageManager.formatMessage("label.url"));
+    Assert.assertEquals(m.getColumnName(3),
+            MessageManager.formatMessage("label.inmenu"));
+    Assert.assertEquals(m.getColumnName(4),
+            MessageManager.formatMessage("label.primary"));
+
+    // check column classes
+    Assert.assertEquals(m.getColumnClass(0), String.class);
+    Assert.assertEquals(m.getColumnClass(1), String.class);
+    Assert.assertEquals(m.getColumnClass(2), String.class);
+    Assert.assertEquals(m.getColumnClass(3), Boolean.class);
+    Assert.assertEquals(m.getColumnClass(4), Boolean.class);
+  }
+
+  /*
+   * Test row insertion
+   */
+  @Test(groups = { "Functional" })
+  public void testRowInsert()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    m.insertRow("newname", "newurl");
+
+    // check table has new row inserted
+    Assert.assertEquals(m.getValueAt(10, 0), "newname");
+    Assert.assertEquals(m.getValueAt(10, 1), "newname");
+    Assert.assertEquals(m.getValueAt(10, 2), "newurl");
+    Assert.assertEquals(m.getValueAt(10, 3), true);
+    Assert.assertEquals(m.getValueAt(10, 4), false);
+
+    // check data source has new row insrte
+    Assert.assertTrue(prov.getLinksForMenu().contains(
+            "newname" + SEP + "newurl"));
+  }
+
+  /*
+   * Test row deletion
+   */
+  @Test(groups = { "Functional" })
+  public void testRowDelete()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    // get name and url at row 0
+    String name = (String) m.getValueAt(0, 0);
+    String url = (String) m.getValueAt(0, 1);
+
+    m.removeRow(0);
+
+    // check table no longer has row 0 elements in it
+    for (int row = 0; row < m.getRowCount(); row++)
+    {
+      Assert.assertNotEquals(m.getValueAt(row, 0), name);
+    }
+
+    // check data source likewise
+    Assert.assertFalse(prov.getLinksForMenu().contains(name + SEP + url));
+  }
+
+  /*
+   * Test value setting and getting
+   */
+  @Test(groups = { "Functional" })
+  public void testValues()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    // get original default
+    int olddefault;
+    boolean isDefault = false;
+    for (olddefault = 0; olddefault < m.getRowCount() && !isDefault; olddefault++)
+    {
+      isDefault = (boolean) m.getValueAt(olddefault, 3);
+    }
+
+    // set new values, one in each row
+    m.setValueAt("dbnamechanged", 6, 0);
+    m.setValueAt("descchanged", 6, 1);
+    m.setValueAt("urlchanged", 7, 2);
+    m.setValueAt(false, 8, 3);
+    m.setValueAt(true, 6, 4);
+
+    m.setValueAt("dbnamechanged", 5, 0);
+
+    // check values updated in table
+    Assert.assertEquals(m.getValueAt(6, 0), "descchanged"); // custom url can't
+                                                            // change db name
+    Assert.assertEquals(m.getValueAt(6, 1), "descchanged");
+    Assert.assertEquals(m.getValueAt(7, 2), "urlchanged");
+    Assert.assertFalse((boolean) m.getValueAt(8, 3));
+    Assert.assertTrue((boolean) m.getValueAt(6, 4));
+    Assert.assertFalse((boolean) m.getValueAt(olddefault, 4));
+
+    Assert.assertEquals(m.getValueAt(5, 0), "dbnamechanged");
+
+    // check default row is exactly one row still
+    for (int row = 0; row < m.getRowCount(); row++)
+    {
+      isDefault = (boolean) m.getValueAt(row, 4);
+
+      // if isDefault is true, row is 9
+      // if isDefault is false, row is not 9
+      Assert.assertFalse(isDefault && !(row == 6));
+    }
+
+    // check table updated
+    Assert.assertTrue(prov.writeUrlsAsString(true).contains(
+            "descchanged" + SEP + m.getValueAt(6, 2)));
+    Assert.assertTrue(prov.writeUrlsAsString(true).contains(
+            m.getValueAt(7, 1) + SEP + "urlchanged"));
+    Assert.assertTrue(prov.writeUrlsAsString(false).contains(
+            (String) m.getValueAt(8, 1)));
+    Assert.assertEquals(prov.getPrimaryUrl("seqid"), m.getValueAt(6, 2)
+            .toString().replace(DELIM + SEQUENCE_ID + DELIM, "seqid"));
+  }
+
+  /*
+   * Test cell editability
+   */
+  @Test(groups = { "Functional" })
+  public void testEditable()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    for (int row = 0; row < m.getRowCount(); row++)
+    {
+      Assert.assertFalse(m.isCellEditable(row, 0));
+      Assert.assertFalse(m.isCellEditable(row, 1));
+      Assert.assertFalse(m.isCellEditable(row, 2));
+      Assert.assertTrue(m.isCellEditable(row, 3));
+
+      if ((row == 4) || (row == 6) || (row == 7))
+      {
+        Assert.assertTrue(m.isCellEditable(row, 4));
+      }
+      else
+      {
+        Assert.assertFalse(m.isCellEditable(row, 4));
+      }
+    }
+  }
+
+  /*
+   * Test row 'deletability'
+   */
+  @Test(groups = { "Functional" })
+  public void testDeletable()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    for (int row = 0; row < m.getRowCount(); row++)
+    {
+      if (row > 4)
+      {
+        Assert.assertTrue(m.isRowDeletable(row));
+      }
+      else
+      {
+        Assert.assertFalse(m.isRowDeletable(row));
+      }
+    }
+  }
+
+  /*
+   * Test indirect row editability
+   */
+  @Test(groups = { "Functional" })
+  public void testRowEditable()
+  {
+    UrlLinkTableModel m = new UrlLinkTableModel(prov);
+
+    for (int row = 0; row < m.getRowCount(); row++)
+    {
+      if (row > 3)
+      {
+        Assert.assertTrue(m.isRowEditable(row));
+      }
+      else
+      {
+        Assert.assertFalse(m.isRowEditable(row));
+      }
+    }
+  }
+}
diff --git a/test/jalview/urls/UrlProviderTest.java b/test/jalview/urls/UrlProviderTest.java
new file mode 100644 (file)
index 0000000..460ebe9
--- /dev/null
@@ -0,0 +1,120 @@
+package jalview.urls;
+
+import jalview.urls.api.UrlProviderI;
+import jalview.urls.desktop.DesktopUrlProviderFactory;
+import jalview.util.UrlConstants;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+
+public class UrlProviderTest {
+  
+  // Test identifiers.org download file
+  private static final String testIdOrgString = "{\"Local\": [{\"id\":\"MIR:00000002\",\"name\":\"ChEBI\",\"pattern\":\"^CHEBI:\\d+$\","
+         + "\"definition\":\"Chemical Entities of Biological Interest (ChEBI)\",\"prefix\":\"chebi\","
+         + "\"url\":\"http://identifiers.org/chebi\"},{\"id\":\"MIR:00000005\",\"name\":\"UniProt Knowledgebase\","
+         + "\"pattern\":\"^([A-N,R-Z][0-9]([A-Z][A-Z, 0-9][A-Z, 0-9][0-9]){1,2})|([O,P,Q][0-9][A-Z, 0-9][A-Z, 0-9][A-Z, 0-9][0-9])(\\.\\d+)?$\","
+         + "\"definition\":\"The UniProt Knowledgebase (UniProtKB)\",\"prefix\":\"uniprot\",\"url\":\"http://identifiers.org/uniprot\"},"
+         + "{\"id\":\"MIR:00000011\",\"name\":\"InterPro\",\"pattern\":\"^IPR\\d{6}$\",\"definition\":\"InterPro\",\"prefix\":\"interpro\","
+         + "\"url\":\"http://identifiers.org/interpro\"},"
+         + "{\"id\":\"MIR:00000372\",\"name\":\"ENA\",\"pattern\":\"^[A-Z]+[0-9]+(\\.\\d+)?$\",\"definition\":\"The European Nucleotide Archive (ENA),\""
+          + "\"prefix\":\"ena.embl\",\"url\":\"http://identifiers.org/ena.embl\"}]}";
+
+  private UrlProviderI prov;
+
+  @BeforeMethod(alwaysRun = true)
+  public void setup()
+  {
+   // make a dummy identifiers.org download file
+   File temp = null;
+
+   try
+   {
+     temp = File.createTempFile("tempfile", ".tmp");
+     temp.deleteOnExit();
+     BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
+     bw.write(testIdOrgString);
+     bw.close();
+   } catch (IOException e)
+   {
+      System.out.println("Error initialising UrlProviderTest test: "
+              + e.getMessage());
+   }
+
+   IdOrgSettings.setDownloadLocation(temp.getPath());
+
+    String defaultUrlString = "No default";
+    String cachedUrlList = "MIR:00000005|MIR:00000011|Test1|http://blah.blah/$SEQUENCE_ID$|"
+            + "Test2|http://test2/$DB_ACCESSION$|Test3|http://test3/$SEQUENCE_ID$";
+    String userUrlList = "MIR:00000372|Test4|httpL//another.url/$SEQUENCE_ID$";
+
+    DesktopUrlProviderFactory factory = new DesktopUrlProviderFactory(
+            defaultUrlString, cachedUrlList, userUrlList);
+    prov = factory.createUrlProvider();
+ }
+
+  @Test(groups = { "Functional" })
+  public void testInitUrlProvider()
+  {
+    String emblUrl = UrlConstants.DEFAULT_STRING.substring(
+            UrlConstants.DEFAULT_STRING.indexOf(UrlConstants.SEP) + 1,
+            UrlConstants.DEFAULT_STRING.length());
+
+    // chooses EMBL url when default Url id does not exist in provided url lists
+    Assert.assertEquals(prov.getPrimaryUrlId(), UrlConstants.DEFAULT_LABEL);
+    Assert.assertEquals(prov.getPrimaryUrl("FER_CAPAN"),
+            emblUrl.replace("$SEQUENCE_ID$", "FER_CAPAN"));
+
+    List<String> menulinks = prov.getLinksForMenu();
+    List<UrlLinkDisplay> allLinks = prov.getLinksForTable();
+
+    // 9 links in provider - 4 from id file, 4 custom links, 1 additional
+    // default
+    Assert.assertEquals(allLinks.size(), 9);
+
+    // 6 links in menu (cachedUrlList) + new default
+    Assert.assertEquals(menulinks.size(), 6);
+
+    Assert.assertTrue(menulinks
+            .contains("Test1|http://blah.blah/$SEQUENCE_ID$"));
+    Assert.assertTrue(menulinks
+            .contains("Test2|http://test2/$DB_ACCESSION$"));
+    Assert.assertTrue(menulinks
+            .contains("Test3|http://test3/$SEQUENCE_ID$"));
+    Assert.assertTrue(menulinks
+            .contains("UniProt Knowledgebase|http://identifiers.org/uniprot/$DB_ACCESSION$|uniprot"));
+    Assert.assertTrue(menulinks
+            .contains("InterPro|http://identifiers.org/interpro/$DB_ACCESSION$|interpro"));
+    Assert.assertTrue(menulinks.contains(UrlConstants.DEFAULT_LABEL
+            + UrlConstants.SEP + emblUrl));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetDefaultUrl()
+  {
+    // set custom url as default
+    Assert.assertTrue(prov.setPrimaryUrl("Test1"));
+    Assert.assertEquals(prov.getPrimaryUrlId(), "Test1");
+
+    // set identifiers url as default
+    Assert.assertTrue(prov.setPrimaryUrl("MIR:00000011"));
+    Assert.assertEquals(prov.getPrimaryUrlId(), "MIR:00000011");
+  }
+
+  @Test(
+    groups = { "Functional" },
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testSetDefaultUrlWrongly()
+  {
+    // don't allow default to be a non-key
+    prov.setPrimaryUrl("not-a-key");
+  }
+}
index d07206f..4092cf2 100644 (file)
@@ -75,8 +75,8 @@ public class UrlLinkTest
             + DELIM + URL_SUFFIX);
     assertEquals(DB, ul.getTarget());
     assertEquals(DB, ul.getLabel());
-    assertEquals(URL_PREFIX, ul.getUrl_prefix());
-    assertEquals(URL_SUFFIX, ul.getUrl_suffix());
+    assertEquals(URL_PREFIX, ul.getUrlPrefix());
+    assertEquals(URL_SUFFIX, ul.getUrlSuffix());
     assertTrue(ul.isDynamic());
     assertFalse(ul.usesDBAccession());
     assertNull(ul.getRegexReplace());
@@ -88,8 +88,8 @@ public class UrlLinkTest
             + URL_SUFFIX);
     assertEquals(DB, ul.getTarget());
     assertEquals(DB, ul.getLabel());
-    assertEquals(URL_PREFIX, ul.getUrl_prefix());
-    assertEquals(URL_SUFFIX, ul.getUrl_suffix());
+    assertEquals(URL_PREFIX, ul.getUrlPrefix());
+    assertEquals(URL_SUFFIX, ul.getUrlSuffix());
     assertTrue(ul.isDynamic());
     assertTrue(ul.usesDBAccession());
     assertNull(ul.getRegexReplace());
@@ -100,7 +100,7 @@ public class UrlLinkTest
     ul = new UrlLink(DB + SEP + URL_PREFIX + URL_SUFFIX.substring(1));
     assertEquals(DB, ul.getTarget());
     assertEquals(DB, ul.getLabel());
-    assertEquals(URL_PREFIX + URL_SUFFIX.substring(1), ul.getUrl_prefix());
+    assertEquals(URL_PREFIX + URL_SUFFIX.substring(1), ul.getUrlPrefix());
     assertFalse(ul.isDynamic());
     assertFalse(ul.usesDBAccession());
     assertNull(ul.getRegexReplace());
@@ -119,8 +119,8 @@ public class UrlLinkTest
             + REGEX_NESTED + DELIM + URL_SUFFIX);
     assertEquals(DB, ul.getTarget());
     assertEquals(DB, ul.getLabel());
-    assertEquals(URL_PREFIX, ul.getUrl_prefix());
-    assertEquals(URL_SUFFIX, ul.getUrl_suffix());
+    assertEquals(URL_PREFIX, ul.getUrlPrefix());
+    assertEquals(URL_SUFFIX, ul.getUrlSuffix());
     assertTrue(ul.isDynamic());
     assertFalse(ul.usesDBAccession());
     assertEquals(REGEX_NESTED.substring(2, REGEX_NESTED.length() - 2),
@@ -133,8 +133,8 @@ public class UrlLinkTest
             + REGEX_NESTED + DELIM + URL_SUFFIX);
     assertEquals(DB, ul.getTarget());
     assertEquals(DB, ul.getLabel());
-    assertEquals(URL_PREFIX, ul.getUrl_prefix());
-    assertEquals(URL_SUFFIX, ul.getUrl_suffix());
+    assertEquals(URL_PREFIX, ul.getUrlPrefix());
+    assertEquals(URL_SUFFIX, ul.getUrlSuffix());
     assertTrue(ul.isDynamic());
     assertTrue(ul.usesDBAccession());
     assertEquals(REGEX_NESTED.substring(2, REGEX_NESTED.length() - 2),
@@ -147,8 +147,8 @@ public class UrlLinkTest
             + REGEX_RUBBISH + DELIM + URL_SUFFIX);
     assertEquals(DB, ul.getTarget());
     assertEquals(DB, ul.getLabel());
-    assertEquals(URL_PREFIX, ul.getUrl_prefix());
-    assertEquals(URL_SUFFIX, ul.getUrl_suffix());
+    assertEquals(URL_PREFIX, ul.getUrlPrefix());
+    assertEquals(URL_SUFFIX, ul.getUrlSuffix());
     assertTrue(ul.isDynamic());
     assertTrue(ul.usesDBAccession());
     assertEquals(REGEX_RUBBISH.substring(2, REGEX_RUBBISH.length() - 2),
@@ -277,10 +277,10 @@ public class UrlLinkTest
     String key = DB + SEP + URL_PREFIX;
     assertEquals(1, linkset.size());
     assertTrue(linkset.containsKey(key));
-    assertEquals(linkset.get(key).get(0), DB);
-    assertEquals(linkset.get(key).get(1), DB);
-    assertEquals(linkset.get(key).get(2), null);
-    assertEquals(linkset.get(key).get(3), URL_PREFIX);
+    assertEquals(DB, linkset.get(key).get(0));
+    assertEquals(DB, linkset.get(key).get(1));
+    assertEquals(null, linkset.get(key).get(2));
+    assertEquals(URL_PREFIX, linkset.get(key).get(3));
   }
 
   /**
@@ -297,10 +297,10 @@ public class UrlLinkTest
     String key = DB + SEP + URL_PREFIX + URL_SUFFIX;
     assertEquals(1, linkset.size());
     assertTrue(linkset.containsKey(key));
-    assertEquals(linkset.get(key).get(0), DB);
-    assertEquals(linkset.get(key).get(1), DB);
-    assertEquals(linkset.get(key).get(2), null);
-    assertEquals(linkset.get(key).get(3), URL_PREFIX + URL_SUFFIX);
+    assertEquals(DB, linkset.get(key).get(0));
+    assertEquals(DB, linkset.get(key).get(1));
+    assertEquals(null, linkset.get(key).get(2));
+    assertEquals(URL_PREFIX + URL_SUFFIX, linkset.get(key).get(3));
   }
 
   /**
@@ -348,11 +348,11 @@ public class UrlLinkTest
             + URL_SUFFIX;
     assertEquals(1, linkset.size());
     assertTrue(linkset.containsKey(key));
-    assertEquals(linkset.get(key).get(0), DB);
-    assertEquals(linkset.get(key).get(1), DB);
-    assertEquals(linkset.get(key).get(2), seq0.getName());
-    assertEquals(linkset.get(key).get(3), URL_PREFIX + seq0.getName()
-            + URL_SUFFIX);
+    assertEquals(DB, linkset.get(key).get(0));
+    assertEquals(DB, linkset.get(key).get(1));
+    assertEquals(seq0.getName(), linkset.get(key).get(2));
+    assertEquals(URL_PREFIX + seq0.getName() + URL_SUFFIX, linkset.get(key)
+            .get(3));
 
     // Test where link takes a db annotation id and only has one dbref
     ul = new UrlLink(links.get(1));
@@ -360,14 +360,14 @@ public class UrlLinkTest
     ul.createLinksFromSeq(seq0, linkset);
 
     key = "P83527|http://www.uniprot.org/uniprot/P83527";
-    assertEquals(1, linkset.size());
+    assertEquals(linkset.size(), 1);
     assertTrue(linkset.containsKey(key));
-    assertEquals(linkset.get(key).get(0), DBRefSource.UNIPROT);
-    assertEquals(linkset.get(key).get(1), DBRefSource.UNIPROT + SEP
-            + "P83527");
-    assertEquals(linkset.get(key).get(2), "P83527");
-    assertEquals(linkset.get(key).get(3),
-            "http://www.uniprot.org/uniprot/P83527");
+    assertEquals(DBRefSource.UNIPROT, linkset.get(key).get(0));
+    assertEquals(DBRefSource.UNIPROT + SEP + "P83527", linkset.get(key)
+            .get(1));
+    assertEquals("P83527", linkset.get(key).get(2));
+    assertEquals("http://www.uniprot.org/uniprot/P83527", linkset.get(key)
+            .get(3));
 
     // Test where link takes a db annotation id and has multiple dbrefs
     ul = new UrlLink(links.get(2));
@@ -378,27 +378,27 @@ public class UrlLinkTest
     // check each link made it in correctly
     key = "IPR001041|http://www.ebi.ac.uk/interpro/entry/IPR001041";
     assertTrue(linkset.containsKey(key));
-    assertEquals(linkset.get(key).get(0), "INTERPRO");
-    assertEquals(linkset.get(key).get(1), "INTERPRO" + SEP + "IPR001041");
-    assertEquals(linkset.get(key).get(2), "IPR001041");
-    assertEquals(linkset.get(key).get(3),
-            "http://www.ebi.ac.uk/interpro/entry/IPR001041");
+    assertEquals("INTERPRO", linkset.get(key).get(0));
+    assertEquals("INTERPRO" + SEP + "IPR001041", linkset.get(key).get(1));
+    assertEquals("IPR001041", linkset.get(key).get(2));
+    assertEquals("http://www.ebi.ac.uk/interpro/entry/IPR001041", linkset
+            .get(key).get(3));
 
     key = "IPR006058|http://www.ebi.ac.uk/interpro/entry/IPR006058";
     assertTrue(linkset.containsKey(key));
-    assertEquals(linkset.get(key).get(0), "INTERPRO");
-    assertEquals(linkset.get(key).get(1), "INTERPRO" + SEP + "IPR006058");
-    assertEquals(linkset.get(key).get(2), "IPR006058");
-    assertEquals(linkset.get(key).get(3),
-            "http://www.ebi.ac.uk/interpro/entry/IPR006058");
+    assertEquals("INTERPRO", linkset.get(key).get(0));
+    assertEquals("INTERPRO" + SEP + "IPR006058", linkset.get(key).get(1));
+    assertEquals("IPR006058", linkset.get(key).get(2));
+    assertEquals("http://www.ebi.ac.uk/interpro/entry/IPR006058", linkset
+            .get(key).get(3));
 
     key = "IPR012675|http://www.ebi.ac.uk/interpro/entry/IPR012675";
     assertTrue(linkset.containsKey(key));
-    assertEquals(linkset.get(key).get(0), "INTERPRO");
-    assertEquals(linkset.get(key).get(1), "INTERPRO" + SEP + "IPR012675");
-    assertEquals(linkset.get(key).get(2), "IPR012675");
-    assertEquals(linkset.get(key).get(3),
-            "http://www.ebi.ac.uk/interpro/entry/IPR012675");
+    assertEquals("INTERPRO", linkset.get(key).get(0));
+    assertEquals("INTERPRO" + SEP + "IPR012675", linkset.get(key).get(1));
+    assertEquals("IPR012675", linkset.get(key).get(2));
+    assertEquals("http://www.ebi.ac.uk/interpro/entry/IPR012675", linkset
+            .get(key).get(3));
 
     // Test where there are no matching dbrefs for the link
     ul = new UrlLink(DB + SEP + URL_PREFIX + DELIM + DB_ACCESSION + DELIM
@@ -408,4 +408,35 @@ public class UrlLinkTest
     assertTrue(linkset.isEmpty());
   }
 
+  /**
+   * Test links where label and target are both included
+   */
+  @Test(groups = { "Functional" })
+  public void testLinksWithTargets()
+  {
+    UrlLink ul = new UrlLink(
+            "Protein Data Bank | http://www.identifiers.org/pdb/$"
+                    + DB_ACCESSION + "$" + " | pdb");
+
+    assertEquals("Protein Data Bank", ul.getLabel());
+    assertEquals("pdb", ul.getTarget());
+    assertEquals("http://www.identifiers.org/pdb/$" + DB_ACCESSION + "$",
+            ul.getUrlWithToken());
+
+    assertEquals("Protein Data Bank|http://www.identifiers.org/pdb/$"
+            + DB_ACCESSION + "$" + "|pdb", ul.toStringWithTarget());
+
+    ul = new UrlLink("Protein Data Bank",
+            "http://www.identifiers.org/pdb/$" + DB_ACCESSION + "$", "pdb");
+
+    assertEquals("Protein Data Bank", ul.getLabel());
+    assertEquals("pdb", ul.getTarget());
+    assertEquals("http://www.identifiers.org/pdb/$" + DB_ACCESSION + "$",
+            ul.getUrlWithToken());
+
+    assertEquals("Protein Data Bank|http://www.identifiers.org/pdb/$"
+            + DB_ACCESSION + "$" + "|pdb", ul.toStringWithTarget());
+
+  }
+
 }
diff --git a/test/jalview/ws/utils/UrlDownloadClientTest.java b/test/jalview/ws/utils/UrlDownloadClientTest.java
new file mode 100644 (file)
index 0000000..2bd8dd0
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.ws.utils;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class UrlDownloadClientTest {
+
+  /**
+   * Test that url is successfully loaded into download file
+   */
+  @Test(groups = { "Network" }, enabled = true)
+  public void UrlDownloadTest()
+  {
+    UrlDownloadClient client = new UrlDownloadClient();
+    String urlstring = "http://identifiers.org/rest/collections/";
+    String outfile = "testfile.tmp";
+
+    try
+    {
+      client.download(urlstring, outfile);
+    } catch (IOException e)
+    {
+      Assert.fail("Exception was thrown from UrlDownloadClient download: "
+              + e.getMessage());
+      File f = new File(outfile);
+      if (f.exists())
+      {
+        f.delete();
+      }
+    }
+
+    // download file exists
+    File f = new File(outfile);
+    Assert.assertTrue(f.exists());
+
+    // download file has a believable size
+    // identifiers.org file typically at least 250K
+    Assert.assertTrue(f.length() > 250000);
+
+    if (f.exists())
+    {
+      f.delete();
+    }
+
+  }
+
+  /**
+   * Test that garbage in results in IOException
+   */
+  @Test(
+    groups = { "Network" },
+    enabled = true,
+    expectedExceptions = { IOException.class })
+  public void DownloadGarbageUrlTest() throws IOException
+  {
+    UrlDownloadClient client = new UrlDownloadClient();
+    String urlstring = "identifiers.org/rest/collections/";
+    String outfile = "testfile.tmp";
+
+    client.download(urlstring, outfile);
+  }
+}