From: Jim Procter Date: Thu, 20 Oct 2016 11:21:33 +0000 (+0100) Subject: Merge branch 'releases/Release_2_10_Branch' into releases/Release_2_10_0_Branch X-Git-Tag: Release_2_10_0b1~4 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=63ac1681563e8abbc3b5fd06e4a6afa6d08dca90;hp=4e1d9a661070768f2b74a8ef7925c38372ee83d6;p=jalview.git Merge branch 'releases/Release_2_10_Branch' into releases/Release_2_10_0_Branch --- diff --git a/examples/testdata/localstruct.pdb b/examples/testdata/localstruct.pdb new file mode 100644 index 0000000..bdf13db --- /dev/null +++ b/examples/testdata/localstruct.pdb @@ -0,0 +1,51 @@ +ATOM 7013 N ALA A 650 -32.039 -14.559 -3.977 1.00 97.16 N +ATOM 7014 CA ALA A 650 -33.253 -15.372 -3.971 1.00101.29 C +ATOM 7015 C ALA A 650 -32.928 -16.865 -4.014 1.00102.48 C +ATOM 7016 O ALA A 650 -33.833 -17.704 -4.075 1.00102.31 O +ATOM 7017 CB ALA A 650 -34.168 -14.987 -5.138 1.00 94.98 C +ATOM 7018 N MET A 651 -31.636 -17.186 -3.978 1.00 98.86 N +ATOM 7019 CA MET A 651 -31.164 -18.568 -4.040 1.00 95.81 C +ATOM 7020 C MET A 651 -29.680 -18.618 -3.678 1.00 95.69 C +ATOM 7021 O MET A 651 -28.847 -18.030 -4.367 1.00 93.27 O +ATOM 7022 CB MET A 651 -31.414 -19.157 -5.435 1.00 94.70 C +ATOM 7023 CG MET A 651 -31.097 -18.189 -6.581 1.00 93.74 C +ATOM 7024 SD MET A 651 -31.780 -18.651 -8.198 1.00 87.88 S +ATOM 7025 CE MET A 651 -30.854 -20.142 -8.594 1.00 81.80 C +ATOM 7026 N LYS A 652 -29.355 -19.313 -2.589 1.00 95.36 N +ATOM 7027 CA LYS A 652 -27.982 -19.355 -2.075 1.00 88.97 C +ATOM 7028 C LYS A 652 -27.021 -20.133 -2.984 1.00 89.03 C +ATOM 7029 O LYS A 652 -27.393 -21.143 -3.592 1.00 90.36 O +ATOM 7030 CB LYS A 652 -27.953 -19.930 -0.656 1.00 85.99 C +ATOM 7031 N ARG A 653 -25.784 -19.655 -3.070 1.00 83.57 N +ATOM 7032 CA ARG A 653 -24.765 -20.305 -3.888 1.00 82.09 C +ATOM 7033 C ARG A 653 -23.704 -20.963 -3.017 1.00 79.82 C +ATOM 7034 O ARG A 653 -23.327 -20.431 -1.977 1.00 79.58 O +ATOM 7035 CB ARG A 653 -24.115 -19.291 -4.831 1.00 84.10 C +ATOM 7036 CG ARG A 653 -23.554 -18.068 -4.129 1.00 82.91 C +ATOM 7037 CD ARG A 653 -23.098 -17.010 -5.124 1.00 84.28 C +ATOM 7038 NE ARG A 653 -23.064 -15.675 -4.527 1.00 83.48 N +ATOM 7039 CZ ARG A 653 -21.959 -15.047 -4.134 1.00 82.34 C +ATOM 7040 NH1 ARG A 653 -20.770 -15.621 -4.277 1.00 80.52 N +ATOM 7041 NH2 ARG A 653 -22.045 -13.836 -3.602 1.00 84.76 N +ATOM 7042 N ARG A 654 -23.219 -22.122 -3.446 1.00 83.16 N +ATOM 7043 CA ARG A 654 -22.263 -22.882 -2.647 1.00 84.47 C +ATOM 7044 C ARG A 654 -20.845 -22.831 -3.207 1.00 82.72 C +ATOM 7045 O ARG A 654 -20.611 -22.306 -4.294 1.00 84.47 O +ATOM 7046 CB ARG A 654 -22.720 -24.337 -2.500 1.00 86.90 C +ATOM 7047 CG ARG A 654 -22.700 -25.156 -3.785 1.00 90.62 C +ATOM 7048 CD ARG A 654 -21.792 -26.372 -3.625 1.00 94.56 C +ATOM 7049 NE ARG A 654 -22.296 -27.559 -4.313 1.00100.64 N +ATOM 7050 CZ ARG A 654 -21.755 -28.770 -4.203 1.00101.77 C +ATOM 7051 NH1 ARG A 654 -20.690 -28.949 -3.433 1.00 99.43 N +ATOM 7052 NH2 ARG A 654 -22.277 -29.803 -4.858 1.00103.21 N +ATOM 7053 N ARG A 655 -19.901 -23.377 -2.452 1.00 80.06 N +ATOM 7054 CA ARG A 655 -18.522 -23.461 -2.899 1.00 81.57 C +ATOM 7055 C ARG A 655 -18.393 -24.576 -3.925 1.00 85.28 C +ATOM 7056 O ARG A 655 -19.120 -25.567 -3.865 1.00 87.19 O +ATOM 7057 CB ARG A 655 -17.617 -23.772 -1.718 1.00 83.19 C +ATOM 7058 CG ARG A 655 -17.720 -25.217 -1.265 1.00 86.49 C +ATOM 7059 CD ARG A 655 -17.099 -25.402 0.093 1.00 85.01 C +ATOM 7060 NE ARG A 655 -15.880 -24.617 0.223 1.00 85.24 N +ATOM 7061 CZ ARG A 655 -15.123 -24.608 1.310 1.00 87.53 C +ATOM 7062 NH1 ARG A 655 -15.465 -25.351 2.354 1.00 87.44 N +ATOM 7063 NH2 ARG A 655 -14.028 -23.864 1.350 1.00 89.64 N diff --git a/help/html/features/structurechooser.html b/help/html/features/structurechooser.html index daa1ac3..fc71826 100644 --- a/help/html/features/structurechooser.html +++ b/help/html/features/structurechooser.html @@ -31,7 +31,7 @@

The Structure Chooser interface allows you to interactively select which PDB structures to view for the currently selected set of - sequences. It's opened by selecting the "3D + sequences. It is opened by selecting the "3D Structure Data.." option from the Sequence ID panel's pop-up menu. The dialog provides: @@ -46,33 +46,47 @@

  • Association of structure data from a local file (in mmCIF or PDB format)
  • - +

    + Selecting and Viewing Structures +

    +

    + Once one or more structures have been selected, pressing the View + button will import them into a new or existing + structure view. +

    Automated discovery of structure data

    -

    After selecting "3D Structure Data ..", Jalview queries the PDB - via the PDBe SOLR Rest API provided by EMBL-EBI to discover PDB ids +

    + After selecting "3D Structure Data ..", Jalview queries the PDB via + the PDBe SOLR Rest API provided by EMBL-EBI to discover PDB IDs associated with the sequence. It does this based on the sequence's - ID string, and any other associated database IDs.

    + ID string, and any other associated database IDs.
    +

    - Exploration of meta-data for available structures + Viewing existing + structures for your sequences +

    +

    + If you have already loaded 3D structure data for the selected + sequences, the structure chooser will first open with the Cached + Structures View. This view shows associations between each + sequence, and chains for 3D structure files already in memory. If + you want to download additional structures, select one of the other + options from the drop down menu.

    -

    Information on each structure available is displayed in columns - in the dialog box. By default, only the title, resolution and PDB - identifier are shown, but many more are provided by the PDBe. To - configure which ones are displayed, select the 'Configure Displayed - Columns' tab and tick the columns which you want to see.

    Selection of the best structure for each sequence

    Jalview can automatically select the best structures according - to meta-data provided by the PDB. By default, the 'Best Quality' - structure for each sequence will be selected, but clicking on the - drop down menu allows other criteria to be chosen, including - Resolution (only defined for X-Ray structures), Highest Protein - Chain etc. When 'Invert' is selected, structures are selected in - reverse order for the current criteria (e.g. worst quality rather - than best).

    + to meta-data provided by the PDB. For alignments with no existing + structure data, the 'Best Quality' structure for each sequence will + by default be selected, but clicking on the drop down menu allows + other criteria to be chosen, including Resolution (only defined for + X-Ray structures), Highest Protein Chain etc. When 'Invert' is + selected, structures are selected in reverse order for the current + criteria (e.g. worst quality rather than best).

    @@ -83,13 +97,20 @@

    -->
    The screenshot above shows the Structure Chooser interface along with the meta-data of auto-discovered structures for the - sample alignment. Note however that if no structures were - auto-discovered, a different interface for manual association will - be invoked as seen in the screenshot below. + sample alignment. If no structures were + auto-discovered, options for manually associating PDB records will be shown (see below). +

    + Exploration of meta-data for available structures +

    +

    Information on each structure available is displayed in columns + in the dialog box. By default, only the title, resolution and PDB + identifier are shown, but many more are provided by the PDBe. To + configure which ones are displayed, select the 'Configure Displayed + Columns' tab and tick the columns which you want to see.

    -

    +
    Manual selection/association of PDB files with Sequences

    @@ -104,14 +125,6 @@ for your sequence. The PDB Rest API, provided by EMBL-EBI, is used to validate and fetch structure data.
    -

    - Viewing existing structures for your sequences -

    -

    - If you have previously associated structure data on the alignment, - selecting Cached PDB Entries from the drop down - menu allows you to select these structures for display. -

    The Structure Chooser interface was introduced in Jalview diff --git a/help/html/features/viewingpdbs.html b/help/html/features/viewingpdbs.html index 21caca1..f60da1a 100755 --- a/help/html/features/viewingpdbs.html +++ b/help/html/features/viewingpdbs.html @@ -53,11 +53,12 @@ 'highest resolution', simply choose another to pick structures in a different way.

  • To view selected structures, click the "View" button. @@ -68,9 +69,14 @@
  • SIFTS records will also be downloaded for mapping UniProt protein sequence data to PDB coordinates.
  • +
  • A new structure viewer will open, or you will be + prompted to add structures to existing viewers (see below for details). +
  • - +

    + Structure Viewers in the Jalview Desktop
    The Jmol viewer has been included since Jalview 2.3. Jalview 2.8.2 included support for @@ -84,10 +90,19 @@ the Annotation from Structure page for more information.

    -

    - If a single PDB structure is selected, one of the - following will happen: + After pressing the + 'View' button in the Structure Chooser
    The behaviour of + the 'View' button depends on the number of structures selected, and + whether structure views already exist for the selected structures or + aligned sequences. +

    +

    + If multiple structures are selected, then Jalview will always create + a new structure view. The selected structures will be imported into + this view, and superposed with the matched positions from the + aligned sequences.
    If a single PDB structure + is selected, one of the following will happen:

      @@ -96,9 +111,10 @@
    • If another structure is already shown for the current alignment, then you will be asked if you want to add and align this structure to the structure - in the existing view. (new feature in Jalview 2.6). -
    • + href="jmol.html#align"> to + the structure in the existing view. (new feature in Jalview + 2.6). +
    • If the structure is already shown, then you will be prompted to associate the sequence with an existing view of the diff --git a/help/html/releases.html b/help/html/releases.html index c547ffb..11c7f58 100755 --- a/help/html/releases.html +++ b/help/html/releases.html @@ -47,6 +47,35 @@
      + 2.10.0b1
      + 25/10/2016
      +
      + + Application +
        +
      • 3D Structure chooser opens with 'Cached structures' view if structures already loaded
      • +
      + +
      + Application +
        +
      • Cannot import or associated local PDB files without a PDB ID HEADER line
      • +
      • RMSD is not output in Jmol console when superposition is performed
      • +
      • Drag and drop of URL from Browser fails for Linux and OSX versions earlier than El Capitan
      • +
      • ENA client ignores invalid content from ENA server
      • +
      • Exceptions are not raised when ENA client attempts to fetch non-existent IDs via Fetch DB Refs UI option
      • +
      • Exceptions are not raised when a new view is created on the alignment
      • +
      + New Known Issues +
      • Drag and drop from URL links in browsers do not work on Windows
      + Build and deployment +
      • URL link checker now copes with multi-line anchor tags
      +
      + + + + +
      2.10.0
      06/10/2016
      diff --git a/resources/embl_mapping.xml b/resources/embl_mapping.xml index 01b921a..3b80821 100644 --- a/resources/embl_mapping.xml +++ b/resources/embl_mapping.xml @@ -28,6 +28,9 @@ --> + + + diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 378fb64..7a1f064 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -1128,7 +1128,7 @@ status.das_feature_fetching_complete = DAS Feature Fetching Complete status.fetching_db_refs = Fetching db refs status.loading_cached_pdb_entries = Loading Cached PDB Entries status.searching_for_pdb_structures = Searching for PDB Structures -status.opening_file = opening file +status.opening_file_for = opening file for status.colouring_chimera = Colouring Chimera label.font_doesnt_have_letters_defined = Font doesn't have letters defined\nso cannot be used\nwith alignment data label.font_too_small = Font size is too small diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 7bea515..cf36638 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -1127,7 +1127,7 @@ action.yes=S label.export_settings=Exportar Ajustes label.linked_view_title=Vista vinculada de cDNA y proteína label.couldnt_read_data=No se pudo leer los datos -status.opening_file=abriendo fichero +status.opening_file_for=abriendo fichero para label.except_selected_sequences=Todo excepto secuencias seleccionadas label.structure_chooser_no_of_structures=Selector de Estructuras - {0} Encontró ({1}) label.search_filter=filtro de búsqueda diff --git a/resources/uniprot_mapping.xml b/resources/uniprot_mapping.xml index a8634af..4a981ad 100755 --- a/resources/uniprot_mapping.xml +++ b/resources/uniprot_mapping.xml @@ -80,7 +80,7 @@ - + diff --git a/src/MCview/PDBChain.java b/src/MCview/PDBChain.java index b74f101..7774dac 100755 --- a/src/MCview/PDBChain.java +++ b/src/MCview/PDBChain.java @@ -199,7 +199,8 @@ public class PDBChain } for (int i = 0; i < features.length; i++) { - if (features[i].getFeatureGroup().equals(pdbid)) + if (features[i].getFeatureGroup() != null + && features[i].getFeatureGroup().equals(pdbid)) { SequenceFeature tx = new SequenceFeature(features[i]); tx.setBegin(1 + residues.elementAt(tx.getBegin() - offset).atoms diff --git a/src/MCview/PDBViewer.java b/src/MCview/PDBViewer.java index 8f014a4..eaa33df 100755 --- a/src/MCview/PDBViewer.java +++ b/src/MCview/PDBViewer.java @@ -128,18 +128,17 @@ public class PDBViewer extends JInternalFrame implements Runnable worker.start(); } - if (pdbentry.getProperty() != null) + String method = (String) pdbentry.getProperty("method"); + if (method != null) { - if (pdbentry.getProperty().get("method") != null) - { - title.append(" Method: "); - title.append(pdbentry.getProperty().get("method")); - } - if (pdbentry.getProperty().get("chains") != null) - { - title.append(" Chain:"); - title.append(pdbentry.getProperty().get("chains")); - } + title.append(" Method: "); + title.append(method); + } + String ch = (String) pdbentry.getProperty("chains"); + if (ch != null) + { + title.append(" Chain:"); + title.append(ch); } Desktop.addInternalFrame(this, title.toString(), 400, 400); } diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index 80984c1..122afa8 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -102,7 +102,6 @@ import java.net.URLEncoder; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; -import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -4024,12 +4023,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, } if (needtoadd) { - // make a note of the access mode and add - if (pdbentry.getProperty() == null) - { - pdbentry.setProperty(new Hashtable()); - } - pdbentry.getProperty().put("protocol", protocol); + pdbentry.setProperty("protocol", protocol); toaddpdb.addPDBId(pdbentry); alignPanel.getStructureSelectionManager() .registerPDBEntry(pdbentry); @@ -4080,7 +4074,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener, if (protocol == null || protocol.trim().length() == 0 || protocol.equals("null")) { - protocol = (String) pdb.getProperty().get("protocol"); + protocol = (String) pdb.getProperty("protocol"); if (protocol == null) { System.err.println("Couldn't work out protocol to open structure: " diff --git a/src/jalview/appletgui/AppletJmol.java b/src/jalview/appletgui/AppletJmol.java index 264ac14..b925284 100644 --- a/src/jalview/appletgui/AppletJmol.java +++ b/src/jalview/appletgui/AppletJmol.java @@ -60,12 +60,9 @@ import java.awt.event.KeyListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; -import java.util.Hashtable; import java.util.List; import java.util.Vector; -import org.jmol.util.Logger; - public class AppletJmol extends EmbmenuFrame implements // StructureListener, KeyListener, ActionListener, ItemListener @@ -271,7 +268,6 @@ public class AppletJmol extends EmbmenuFrame implements jmb.allocateViewer(renderPanel, true, ap.av.applet.getName() + "_jmol_", ap.av.applet.getDocumentBase(), ap.av.applet.getCodeBase(), "-applet", scriptWindow, null); - Logger.setLogLevel(Logger.LEVEL_WARN); } catch (Exception e) { System.err @@ -293,11 +289,8 @@ public class AppletJmol extends EmbmenuFrame implements closeViewer(); } }); - if (pdbentry.getProperty() == null) - { - pdbentry.setProperty(new Hashtable()); - pdbentry.getProperty().put("protocol", protocol); - } + pdbentry.setProperty("protocol", protocol); + if (pdbentry.getFile() != null) { // import structure data from pdbentry.getFile based on given protocol diff --git a/src/jalview/datamodel/PDBEntry.java b/src/jalview/datamodel/PDBEntry.java index 1403595..6a6ccd0 100755 --- a/src/jalview/datamodel/PDBEntry.java +++ b/src/jalview/datamodel/PDBEntry.java @@ -22,10 +22,20 @@ package jalview.datamodel; import jalview.util.CaseInsensitiveString; +import java.util.Collections; +import java.util.Enumeration; import java.util.Hashtable; public class PDBEntry { + + /** + * constant for storing chain code in properties table + */ + private static final String CHAIN_ID = "chain_code"; + + private Hashtable properties; + private static final int PDB_ID_LENGTH = 4; private String file; @@ -67,12 +77,6 @@ public class PDBEntry } } - /** - * constant for storing chain code in properties table - */ - private static final String CHAIN_ID = "chain_code"; - - Hashtable properties; /** * Answers true if obj is a PDBEntry with the same id and chain code (both @@ -137,13 +141,13 @@ public class PDBEntry /** * @param pdbId * @param chain - * @param type + * @param entryType * @param filePath */ - void init(String pdbId, String chain, PDBEntry.Type type, String filePath) + void init(String pdbId, String chain, PDBEntry.Type entryType, String filePath) { this.id = pdbId; - this.type = type == null ? null : type.toString(); + this.type = entryType == null ? null : entryType.toString(); this.file = filePath; setChainCode(chain); } @@ -160,7 +164,7 @@ public class PDBEntry id = entry.id; if (entry.properties != null) { - properties = (Hashtable) entry.properties.clone(); + properties = (Hashtable) entry.properties.clone(); } } @@ -193,9 +197,9 @@ public class PDBEntry init(pdbId, chainCode, null, null); } - public void setFile(String file) + public void setFile(String f) { - this.file = file; + this.file = f; } public String getFile() @@ -228,14 +232,33 @@ public class PDBEntry return id; } - public void setProperty(Hashtable property) + public void setProperty(String key, Object value) { - this.properties = property; + if (this.properties == null) + { + this.properties = new Hashtable(); + } + properties.put(key, value); } - public Hashtable getProperty() + public Object getProperty(String key) { - return properties; + return properties == null ? null : properties.get(key); + } + + /** + * Returns an enumeration of the keys of this object's properties (or an empty + * enumeration if it has no properties) + * + * @return + */ + public Enumeration getProperties() + { + if (properties == null) + { + return Collections.emptyEnumeration(); + } + return properties.keys(); } /** @@ -248,24 +271,37 @@ public class PDBEntry : properties.get(CHAIN_ID).toString(); } + /** + * Sets a non-case-sensitive property for the given chain code. Two PDBEntry + * objects which differ only in the case of their chain code are considered + * equal. This avoids duplication of objects in lists of PDB ids. + * + * @param chainCode + */ public void setChainCode(String chainCode) { - if (properties == null) + if (chainCode == null) { - if (chainCode == null) - { - // nothing to do. - return; - } - properties = new Hashtable(); + deleteProperty(CHAIN_ID); } - if (chainCode == null) + else + { + setProperty(CHAIN_ID, new CaseInsensitiveString(chainCode)); + } + } + + /** + * Deletes the property with the given key, and returns the deleted value (or + * null) + */ + Object deleteProperty(String key) + { + Object result = null; + if (properties != null) { - properties.remove(CHAIN_ID); - return; + result = properties.remove(key); } - // update property for non-null chainCode - properties.put(CHAIN_ID, new CaseInsensitiveString(chainCode)); + return result; } @Override @@ -275,6 +311,35 @@ public class PDBEntry } /** + * Getter provided for Castor binding only. Application code should call + * getProperty() or getProperties() instead. + * + * @deprecated + * @see #getProperty(String) + * @see #getProperties() + * @see jalview.ws.dbsources.Uniprot#getUniprotEntries + * @return + */ + @Deprecated + public Hashtable getProps() + { + return properties; + } + + /** + * Setter provided for Castor binding only. Application code should call + * setProperty() instead. + * + * @deprecated + * @return + */ + @Deprecated + public void setProps(Hashtable props) + { + properties = props; + } + + /** * Answers true if this object is either equivalent to, or can be 'improved' * by, the given entry. *

      @@ -285,7 +350,7 @@ public class PDBEntry * @param newEntry * @return true if modifications were made */ - protected boolean updateFrom(PDBEntry newEntry) + public boolean updateFrom(PDBEntry newEntry) { if (this.equals(newEntry)) { @@ -299,7 +364,7 @@ public class PDBEntry } /* - * id (less any chain code) has to match (ignoring case) + * id has to match (ignoring case) */ if (!getId().equalsIgnoreCase(newId)) { @@ -356,26 +421,20 @@ public class PDBEntry } /* - * copy any new properties; notice this may include chain_code, - * but we excluded differing chain codes earlier + * copy any new or modified properties */ - if (newEntry.getProperty() != null) + Enumeration newProps = newEntry.getProperties(); + while (newProps.hasMoreElements()) { - if (properties == null) + /* + * copy properties unless value matches; this defends against changing + * the case of chain_code which is wrapped in a CaseInsensitiveString + */ + String key = newProps.nextElement(); + Object value = newEntry.getProperty(key); + if (!value.equals(getProperty(key))) { - properties = new Hashtable(); - } - for (Object p : newEntry.getProperty().keySet()) - { - /* - * copy properties unless value matches; this defends against changing - * the case of chain_code which is wrapped in a CaseInsensitiveString - */ - Object value = newEntry.getProperty().get(p); - if (!value.equals(properties.get(p))) - { - properties.put(p, newEntry.getProperty().get(p)); - } + setProperty(key, value); } } return true; diff --git a/src/jalview/datamodel/xdb/embl/EmblFile.java b/src/jalview/datamodel/xdb/embl/EmblFile.java index 69870b6..1dd854a 100644 --- a/src/jalview/datamodel/xdb/embl/EmblFile.java +++ b/src/jalview/datamodel/xdb/embl/EmblFile.java @@ -46,6 +46,8 @@ public class EmblFile Vector errors; + String text; + /** * @return the entries */ @@ -152,6 +154,10 @@ public class EmblFile */ static void canonicaliseDbRefs(EmblFile record) { + if (record.getEntries() == null) + { + return; + } for (EmblEntry entry : record.getEntries()) { if (entry.getDbRefs() != null) @@ -183,4 +189,14 @@ public class EmblFile } } } + + public String getText() + { + return text; + } + + public void setText(String text) + { + this.text = text; + } } diff --git a/src/jalview/ext/ensembl/EnsemblRestClient.java b/src/jalview/ext/ensembl/EnsemblRestClient.java index b7b1985..5903f69 100644 --- a/src/jalview/ext/ensembl/EnsemblRestClient.java +++ b/src/jalview/ext/ensembl/EnsemblRestClient.java @@ -50,6 +50,10 @@ import com.stevesoft.pat.Regex; */ abstract class EnsemblRestClient extends EnsemblSequenceFetcher { + private static final int DEFAULT_READ_TIMEOUT = 5 * 60 * 1000; // 5 minutes + + private static final int CONNECT_TIMEOUT_MS = 10 * 1000; // 10 seconds + /* * update these constants when Jalview has been checked / updated for * changes to Ensembl REST API @@ -186,7 +190,7 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher */ private boolean checkEnsembl() { - HttpURLConnection conn = null; + BufferedReader br = null; try { // note this format works for both ensembl and ensemblgenomes @@ -196,8 +200,9 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher /* * expect {"ping":1} if ok + * if ping takes more than 2 seconds to respond, treat as if unavailable */ - BufferedReader br = getHttpResponse(ping, null); + br = getHttpResponse(ping, null, 2 * 1000); JSONParser jp = new JSONParser(); JSONObject val = (JSONObject) jp.parse(br); String pingString = val.get("ping").toString(); @@ -208,9 +213,15 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher + t.getMessage()); } finally { - if (conn != null) + if (br != null) { - conn.disconnect(); + try + { + br.close(); + } catch (IOException e) + { + // ignore + } } } return false; @@ -239,17 +250,34 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher } /** + * Gets a reader to the HTTP response, using the default read timeout of 5 + * minutes + * + * @param url + * @param ids + * @return + * @throws IOException + */ + protected BufferedReader getHttpResponse(URL url, List ids) + throws IOException + { + return getHttpResponse(url, ids, DEFAULT_READ_TIMEOUT); + } + + /** * Writes the HTTP request and gets the response as a reader. * * @param url * @param ids * written as Json POST body if more than one + * @param readTimeout + * in milliseconds * @return * @throws IOException * if response code was not 200, or other I/O error */ - protected BufferedReader getHttpResponse(URL url, List ids) - throws IOException + protected BufferedReader getHttpResponse(URL url, List ids, + int readTimeout) throws IOException { // long now = System.currentTimeMillis(); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); @@ -269,6 +297,9 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher connection.setDoInput(true); connection.setDoOutput(multipleIds); + connection.setConnectTimeout(CONNECT_TIMEOUT_MS); + connection.setReadTimeout(readTimeout); + if (multipleIds) { writePostBody(connection, ids); diff --git a/src/jalview/ext/jmol/JalviewJmolBinding.java b/src/jalview/ext/jmol/JalviewJmolBinding.java index 2f3464b..fbac400 100644 --- a/src/jalview/ext/jmol/JalviewJmolBinding.java +++ b/src/jalview/ext/jmol/JalviewJmolBinding.java @@ -43,6 +43,7 @@ import java.awt.event.ComponentListener; import java.io.File; import java.net.URL; import java.security.AccessControlException; +import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -93,11 +94,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel boolean loadedInline; - /** - * current set of model filenames loaded in the Jmol instance - */ - String[] modelFileNames = null; - StringBuffer resetLastRes = new StringBuffer(); public Viewer viewer; @@ -258,8 +254,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } catch (InterruptedException i) { } - ; } + + /* + * get the distinct structure files modelled + * (a file with multiple chains may map to multiple sequences) + */ String[] files = getPdbFile(); if (!waitForFileLoad(files)) { @@ -307,6 +307,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel * 'matched' array will hold 'true' for visible alignment columns where * all sequences have a residue with a mapping to the PDB structure */ + // TODO could use a BitSet for matched boolean matched[] = new boolean[alignment.getWidth()]; for (int m = 0; m < matched.length; m++) { @@ -352,6 +353,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel * generate select statements to select regions to superimpose structures */ { + // TODO extract method to construct selection statements for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { String chainCd = ":" + structures[pdbfnum].chain; @@ -419,6 +421,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } } StringBuilder command = new StringBuilder(256); + // command.append("set spinFps 10;\n"); + for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { if (pdbfnum == refStructure || selcom[pdbfnum] == null @@ -449,6 +453,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } if (selectioncom.length() > 0) { + // TODO is performing selectioncom redundant here? is done later on // System.out.println("Select regions:\n" + selectioncom.toString()); evalStateCommand("select *; cartoons off; backbone; select (" + selectioncom.toString() + "); cartoons; "); @@ -647,15 +652,15 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel } if (modelFileNames == null) { - String mset[] = new String[viewer.ms.mc]; - _modelFileNameMap = new int[mset.length]; + List mset = new ArrayList(); + _modelFileNameMap = new int[viewer.ms.mc]; String m = viewer.ms.getModelFileName(0); if (m != null) { - mset[0] = m; + String filePath = m; try { - mset[0] = new File(m).getAbsolutePath(); + filePath = new File(m).getAbsolutePath(); } catch (AccessControlException x) { // usually not allowed to do this in applet @@ -663,39 +668,43 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel .println("jmolBinding: Using local file string from Jmol: " + m); } - if (mset[0].indexOf("/file:") != -1) + if (filePath.indexOf("/file:") != -1) { // applet path with docroot - discard as format won't match pdbfile - mset[0] = m; + filePath = m; } + mset.add(filePath); _modelFileNameMap[0] = 0; // filename index for first model is always 0. } int j = 1; - for (int i = 1; i < mset.length; i++) + for (int i = 1; i < viewer.ms.mc; i++) { m = viewer.ms.getModelFileName(i); - mset[j] = m; + String filePath = m; if (m != null) { try { - mset[j] = new File(m).getAbsolutePath(); + filePath = new File(m).getAbsolutePath(); } catch (AccessControlException x) { // usually not allowed to do this in applet, so keep raw handle // System.err.println("jmolBinding: Using local file string from Jmol: "+m); } } - _modelFileNameMap[j] = i; // record the model index for the filename - // skip any additional models in the same file (NMR structures) - if ((mset[j] == null ? mset[j] != mset[j - 1] - : (mset[j - 1] == null || !mset[j].equals(mset[j - 1])))) + + /* + * add this model unless it is read from a structure file we have + * already seen (example: 2MJW is an NMR structure with 10 models) + */ + if (!mset.contains(filePath)) { + mset.add(filePath); + _modelFileNameMap[j] = i; // record the model index for the filename j++; } } - modelFileNames = new String[j]; - System.arraycopy(mset, 0, modelFileNames, 0, j); + modelFileNames = mset.toArray(new String[mset.size()]); } return modelFileNames; } diff --git a/src/jalview/ext/jmol/JmolParser.java b/src/jalview/ext/jmol/JmolParser.java index 1800ef0..b2ba256 100644 --- a/src/jalview/ext/jmol/JmolParser.java +++ b/src/jalview/ext/jmol/JmolParser.java @@ -124,12 +124,11 @@ public class JmolParser extends StructureFile implements JmolStatusListener try { /* - * params -o (output to sysout) -i (no info logging, less verbose) - * -n (nodisplay) -x (exit when finished) + * params -o (output to sysout) -n (nodisplay) -x (exit when finished) * see http://wiki.jmol.org/index.php/Jmol_Application */ viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null, - null, "-x -o -n -i", this); + null, "-x -o -n", this); // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used viewer.setBooleanProperty("defaultStructureDSSP", true); } catch (ClassCastException x) @@ -151,7 +150,17 @@ public class JmolParser extends StructureFile implements JmolStatusListener List prot = new ArrayList(); PDBChain tmpchain; String pdbId = (String) ms.getInfo(0, "title"); - setId(pdbId); + + if (pdbId == null) + { + setId(safeName(getDataName())); + setPDBIdAvailable(false); + } + else + { + setId(pdbId); + setPDBIdAvailable(true); + } List significantAtoms = convertSignificantAtoms(ms); for (Atom tmpatom : significantAtoms) { @@ -166,7 +175,7 @@ public class JmolParser extends StructureFile implements JmolStatusListener tmpchain.atoms.addElement(tmpatom); } catch (Exception e) { - tmpchain = new PDBChain(pdbId, tmpatom.chain); + tmpchain = new PDBChain(getId(), tmpatom.chain); getChains().add(tmpchain); tmpchain.atoms.addElement(tmpatom); } @@ -177,10 +186,6 @@ public class JmolParser extends StructureFile implements JmolStatusListener makeResidueList(); makeCaBondList(); - if (getId() == null) - { - setId(safeName(getDataName())); - } for (PDBChain chain : getChains()) { SequenceI chainseq = postProcessChain(chain); diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index 944ef52..7ba9186 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -101,11 +101,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel private String lastCommand; - /* - * current set of model filenames loaded - */ - String[] modelFileNames = null; - String lastHighlightCommand; /* diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 672f7ac..63620e5 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -108,6 +108,7 @@ import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; +import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; @@ -4814,6 +4815,9 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override public void drop(DropTargetDropEvent evt) { + // JAL-1552 - acceptDrop required before getTransferable call for + // Java's Transferable for native dnd + evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); Transferable t = evt.getTransferable(); java.util.List files = new ArrayList(), protocols = new ArrayList(); diff --git a/src/jalview/gui/AppJmol.java b/src/jalview/gui/AppJmol.java index 24604ed..1c0dfe6 100644 --- a/src/jalview/gui/AppJmol.java +++ b/src/jalview/gui/AppJmol.java @@ -79,6 +79,9 @@ import javax.swing.event.MenuListener; public class AppJmol extends StructureViewerBase { + // ms to wait for Jmol to load files + private static final int JMOL_LOAD_TIMEOUT = 20000; + private static final String SPACE = " "; private static final String BACKSLASH = "\""; @@ -309,12 +312,10 @@ public class AppJmol extends StructureViewerBase jmb.setColourBySequence(true); setSize(400, 400); // probably should be a configurable/dynamic default here initMenus(); - worker = null; - { - addingStructures = false; - worker = new Thread(this); - worker.start(); - } + addingStructures = false; + worker = new Thread(this); + worker.start(); + this.addInternalFrameListener(new InternalFrameAdapter() { @Override @@ -381,10 +382,7 @@ public class AppJmol extends StructureViewerBase scriptWindow.setVisible(false); } - /* - * -i for no info logging (less verbose) - */ - jmb.allocateViewer(renderPanel, true, "", null, null, "-i", + jmb.allocateViewer(renderPanel, true, "", null, null, "", scriptWindow, null); // jmb.newJmolPopup("Jmol"); if (command == null) @@ -554,7 +552,7 @@ public class AppJmol extends StructureViewerBase } // need to wait around until script has finished - int waitMax = 5000; // give up after 5 seconds + int waitMax = JMOL_LOAD_TIMEOUT; int waitFor = 35; int waitTotal = 0; while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled() @@ -571,7 +569,13 @@ public class AppJmol extends StructureViewerBase } if (waitTotal > waitMax) { - System.err.println("Timed out waiting for Jmol to load files"); + System.err + .println("Timed out waiting for Jmol to load files after " + + waitTotal + "ms"); +// System.err.println("finished: " + jmb.isFinishedInit() +// + "; loaded: " + Arrays.toString(jmb.getPdbFile()) +// + "; files: " + files.toString()); + jmb.getPdbFile(); break; } } diff --git a/src/jalview/gui/ChimeraViewFrame.java b/src/jalview/gui/ChimeraViewFrame.java index 592f56c..c30a418 100644 --- a/src/jalview/gui/ChimeraViewFrame.java +++ b/src/jalview/gui/ChimeraViewFrame.java @@ -660,7 +660,8 @@ public class ChimeraViewFrame extends StructureViewerBase { int pos = filePDBpos.get(num).intValue(); long startTime = startProgressBar("Chimera " - + MessageManager.getString("status.opening_file")); + + MessageManager.getString("status.opening_file_for") + + " " + pe.getId()); jmb.openFile(pe); jmb.addSequence(pos, jmb.getSequence()[pos]); File fl = new File(pe.getFile()); diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index f99af34..3f457ea 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -941,6 +941,9 @@ public class Desktop extends jalview.jbgui.GDesktop implements public void drop(DropTargetDropEvent evt) { boolean success = true; + // JAL-1552 - acceptDrop required before getTransferable call for + // Java's Transferable for native dnd + evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); Transferable t = evt.getTransferable(); java.util.List files = new ArrayList(); java.util.List protocols = new ArrayList(); @@ -3180,7 +3183,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements { // Works on Windows and MacOSX Cache.log.debug("Drop handled as javaFileListFlavor"); - evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); for (Object file : (List) t .getTransferData(DataFlavor.javaFileListFlavor)) { @@ -3197,7 +3199,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements { Cache.log.debug("Drop handled as uriListFlavor"); // This is used by Unix drag system - evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); data = (String) t.getTransferData(uriListFlavor); } if (data == null) @@ -3252,7 +3253,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements { Cache.log.debug("Supported transfer dataflavor: " + fl.toString()); - evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); Object df = t.getTransferData(fl); if (df != null) { diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index 9a8e5f6..1c90889 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -992,17 +992,16 @@ public class Jalview2XML } } - if (entry.getProperty() != null && !entry.getProperty().isEmpty()) + Enumeration props = entry.getProperties(); + if (props.hasMoreElements()) { PdbentryItem item = new PdbentryItem(); - Hashtable properties = entry.getProperty(); - Enumeration en2 = properties.keys(); - while (en2.hasMoreElements()) + while (props.hasMoreElements()) { Property prop = new Property(); - String key = en2.nextElement().toString(); + String key = props.nextElement(); prop.setName(key); - prop.setValue(properties.get(key).toString()); + prop.setValue(entry.getProperty(key).toString()); item.addProperty(prop); } pdb.addPdbentryItem(item); @@ -3018,7 +3017,8 @@ public class Jalview2XML entry.setType(PDBEntry.Type.FILE); } } - if (ids[p].getFile() != null) + // jprovider is null when executing 'New View' + if (ids[p].getFile() != null && jprovider != null) { if (!pdbloaded.containsKey(ids[p].getFile())) { @@ -3032,12 +3032,11 @@ public class Jalview2XML } if (ids[p].getPdbentryItem() != null) { - entry.setProperty(new Hashtable()); for (PdbentryItem item : ids[p].getPdbentryItem()) { for (Property pr : item.getProperty()) { - entry.getProperty().put(pr.getName(), pr.getValue()); + entry.setProperty(pr.getName(), pr.getValue()); } } } diff --git a/src/jalview/gui/StructureChooser.java b/src/jalview/gui/StructureChooser.java index 57debe3..3350f6c 100644 --- a/src/jalview/gui/StructureChooser.java +++ b/src/jalview/gui/StructureChooser.java @@ -81,6 +81,8 @@ public class StructureChooser extends GStructureChooser implements private boolean isValidPBDEntry; + private boolean cachedPDBExists; + public StructureChooser(SequenceI[] selectedSeqs, SequenceI selectedSeq, AlignmentPanel ap) { @@ -102,7 +104,7 @@ public class StructureChooser extends GStructureChooser implements } // ensure a filter option is in force for search - populateFilterComboBox(true); + populateFilterComboBox(true, cachedPDBExists); Thread discoverPDBStructuresThread = new Thread(new Runnable() { @Override @@ -118,7 +120,7 @@ public class StructureChooser extends GStructureChooser implements startTime); fetchStructuresMetaData(); // revise filter options if no results were found - populateFilterComboBox(isStructuresDiscovered()); + populateFilterComboBox(isStructuresDiscovered(), cachedPDBExists); updateProgressIndicator(null, startTime); mainFrame.setVisible(true); updateCurrentView(); @@ -237,7 +239,7 @@ public class StructureChooser extends GStructureChooser implements } } } - + cachedPDBExists = !entries.isEmpty(); PDBEntryTableModel tableModelx = new PDBEntryTableModel(entries); tbl_local_pdb.setModel(tableModelx); } @@ -524,7 +526,8 @@ public class StructureChooser extends GStructureChooser implements * Populates the filter combo-box options dynamically depending on discovered * structures */ - protected void populateFilterComboBox(boolean haveData) + protected void populateFilterComboBox(boolean haveData, + boolean cachedPDBExists) { /* * temporarily suspend the change listener behaviour @@ -549,8 +552,14 @@ public class StructureChooser extends GStructureChooser implements VIEWS_ENTER_ID)); cmb_filterOption.addItem(new FilterOption("From File", "-", VIEWS_FROM_FILE)); - cmb_filterOption.addItem(new FilterOption("Cached PDB Entries", "-", - VIEWS_LOCAL_PDB)); + FilterOption cachedOption = new FilterOption("Cached PDB Entries", "-", + VIEWS_LOCAL_PDB); + cmb_filterOption.addItem(cachedOption); + + if (/*!haveData &&*/cachedPDBExists) + { + cmb_filterOption.setSelectedItem(cachedOption); + } cmb_filterOption.addItemListener(this); } diff --git a/src/jalview/gui/StructureViewer.java b/src/jalview/gui/StructureViewer.java index 21b2984..189d490 100644 --- a/src/jalview/gui/StructureViewer.java +++ b/src/jalview/gui/StructureViewer.java @@ -136,14 +136,16 @@ public class StructureViewer protected JalviewStructureDisplayI viewStructures(ViewerType viewerType, PDBEntry[] pdbs, SequenceI[][] seqsForPdbs, AlignmentPanel ap) { + PDBEntry[] pdbsForFile = getUniquePdbFiles(pdbs); JalviewStructureDisplayI sview = null; if (viewerType.equals(ViewerType.JMOL)) { - sview = new AppJmol(ap, pdbs, ap.av.collateForPDB(pdbs)); + sview = new AppJmol(ap, pdbsForFile, ap.av.collateForPDB(pdbsForFile)); } else if (viewerType.equals(ViewerType.CHIMERA)) { - sview = new ChimeraViewFrame(pdbs, ap.av.collateForPDB(pdbs), ap); + sview = new ChimeraViewFrame(pdbsForFile, + ap.av.collateForPDB(pdbsForFile), ap); } else { @@ -153,6 +155,36 @@ public class StructureViewer return sview; } + /** + * Convert the array of PDBEntry into an array with no filename repeated + * + * @param pdbs + * @return + */ + static PDBEntry[] getUniquePdbFiles(PDBEntry[] pdbs) + { + if (pdbs == null) + { + return null; + } + List uniques = new ArrayList(); + List filesSeen = new ArrayList(); + for (PDBEntry entry : pdbs) + { + String file = entry.getFile(); + if (file == null) + { + uniques.add(entry); + } + else if (!filesSeen.contains(file)) + { + uniques.add(entry); + filesSeen.add(file); + } + } + return uniques.toArray(new PDBEntry[uniques.size()]); + } + protected JalviewStructureDisplayI viewStructures(ViewerType viewerType, PDBEntry pdb, SequenceI[] seqsForPdb, AlignmentPanel ap) { diff --git a/src/jalview/io/StructureFile.java b/src/jalview/io/StructureFile.java index 61a9b48..26c202c 100644 --- a/src/jalview/io/StructureFile.java +++ b/src/jalview/io/StructureFile.java @@ -35,7 +35,6 @@ import jalview.structure.StructureImportSettings; import java.awt.Color; import java.io.IOException; import java.lang.reflect.Constructor; -import java.util.Hashtable; import java.util.List; import java.util.Vector; @@ -68,6 +67,8 @@ public abstract class StructureFile extends AlignFile private Vector chains; + private boolean pdbIdAvailable; + public StructureFile(String inFile, String type) throws IOException { super(inFile, type); @@ -112,7 +113,6 @@ public abstract class StructureFile extends AlignFile { } - @SuppressWarnings("rawtypes") protected SequenceI postProcessChain(PDBChain chain) { SequenceI pdbSequence = chain.sequence; @@ -120,10 +120,9 @@ public abstract class StructureFile extends AlignFile PDBEntry entry = new PDBEntry(); entry.setId(getId()); entry.setType(getStructureFileType()); - entry.setProperty(new Hashtable()); if (chain.id != null) { - entry.setChainCode(String.valueOf(chain.id)); + entry.setChainCode(chain.id); } if (inFile != null) { @@ -470,4 +469,19 @@ public abstract class StructureFile extends AlignFile { return new PDBFeatureSettings(); } + + /** + * Answers true if the structure file has a PDBId + * + * @return + */ + public boolean isPPDBIdAvailable() + { + return pdbIdAvailable; + } + + public void setPDBIdAvailable(boolean pdbIdAvailable) + { + this.pdbIdAvailable = pdbIdAvailable; + } } diff --git a/src/jalview/structure/StructureSelectionManager.java b/src/jalview/structure/StructureSelectionManager.java index 612b168..7e691be 100644 --- a/src/jalview/structure/StructureSelectionManager.java +++ b/src/jalview/structure/StructureSelectionManager.java @@ -390,6 +390,9 @@ public class StructureSelectionManager { registerPDBFile(pdb.getId().trim(), pdbFile); } + // if PDBId is unavailable then skip SIFTS mapping execution path + isMapUsingSIFTs = pdb.isPPDBIdAvailable(); + } catch (Exception ex) { ex.printStackTrace(); diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java index dc42315..b00f1bc 100644 --- a/src/jalview/structures/models/AAStructureBindingModel.java +++ b/src/jalview/structures/models/AAStructureBindingModel.java @@ -51,6 +51,10 @@ public abstract class AAStructureBindingModel extends private StructureSelectionManager ssm; + /* + * distinct PDB entries (pdb files) associated + * with sequences + */ private PDBEntry[] pdbEntry; /* @@ -75,6 +79,11 @@ public abstract class AAStructureBindingModel extends private boolean finishedInit = false; /** + * current set of model filenames loaded in the Jmol instance + */ + protected String[] modelFileNames = null; + + /** * Data bean class to simplify parameterisation in superposeStructures */ protected class SuperposeData @@ -239,24 +248,21 @@ public abstract class AAStructureBindingModel extends // TODO: give a more informative title when multiple structures are // displayed. StringBuilder title = new StringBuilder(64); - final PDBEntry pdbEntry = getPdbEntry(0); + final PDBEntry pdbe = getPdbEntry(0); title.append(viewerName + " view for " + getSequence()[0][0].getName() - + ":" + pdbEntry.getId()); + + ":" + pdbe.getId()); if (verbose) { - if (pdbEntry.getProperty() != null) + String method = (String) pdbe.getProperty("method"); + if (method != null) { - if (pdbEntry.getProperty().get("method") != null) - { - title.append(" Method: "); - title.append(pdbEntry.getProperty().get("method")); - } - if (pdbEntry.getProperty().get("chains") != null) - { - title.append(" Chain:"); - title.append(pdbEntry.getProperty().get("chains")); - } + title.append(" Method: ").append(method); + } + String chain = (String) pdbe.getProperty("chains"); + if (chain != null) + { + title.append(" Chain:").append(chain); } } return title.toString(); @@ -521,6 +527,10 @@ public abstract class AAStructureBindingModel extends { int refStructure = -1; String[] files = getPdbFile(); + if (files == null) + { + return -1; + } for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]); @@ -565,7 +575,11 @@ public abstract class AAStructureBindingModel extends } structures[pdbfnum].pdbId = mapping.getPdbId(); structures[pdbfnum].isRna = theSequence.getRNA() != null; - // move on to next pdb file + + /* + * move on to next pdb file (ignore sequences for other chains + * for the same structure) + */ s = seqCountForPdbFile; break; } diff --git a/src/jalview/util/DBRefUtils.java b/src/jalview/util/DBRefUtils.java index d43f5bc..e6aa472 100755 --- a/src/jalview/util/DBRefUtils.java +++ b/src/jalview/util/DBRefUtils.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; @@ -508,9 +507,7 @@ public class DBRefUtils PDBEntry pdbr = new PDBEntry(); pdbr.setId(pdbid); pdbr.setType(PDBEntry.Type.PDB); - pdbr.setProperty(new Hashtable()); pdbr.setChainCode(chaincode); - // pdbr.getProperty().put("CHAIN", chaincode); seq.addPDBId(pdbr); } else diff --git a/src/jalview/ws/dbsources/EmblXmlSource.java b/src/jalview/ws/dbsources/EmblXmlSource.java index 2049766..b139574 100644 --- a/src/jalview/ws/dbsources/EmblXmlSource.java +++ b/src/jalview/ws/dbsources/EmblXmlSource.java @@ -102,8 +102,13 @@ public abstract class EmblXmlSource extends EbiFileRetrievedProxy } } + /* + * invalid accession gets a reply with no elements, text content of + * EmbFile reads something like (e.g.) this ungrammatical phrase + * Entry: display type is either not supported or entry is not found. + */ List peptides = new ArrayList(); - if (efile != null) + if (efile != null && efile.getEntries() != null) { for (EmblEntry entry : efile.getEntries()) { diff --git a/src/jalview/ws/dbsources/Uniprot.java b/src/jalview/ws/dbsources/Uniprot.java index 4030d8c..b6f53cd 100644 --- a/src/jalview/ws/dbsources/Uniprot.java +++ b/src/jalview/ws/dbsources/Uniprot.java @@ -226,8 +226,7 @@ public class Uniprot extends DbSourceProxyImpl if ("EMBL".equals(pdb.getType())) { // look for a CDS reference and add it, too. - String cdsId = (String) pdb.getProperty() - .get("protein sequence ID"); + String cdsId = (String) pdb.getProperty("protein sequence ID"); if (cdsId != null && cdsId.trim().length() > 0) { // remove version @@ -246,8 +245,7 @@ public class Uniprot extends DbSourceProxyImpl * * */ - String cdsId = (String) pdb.getProperty() - .get("protein sequence ID"); + String cdsId = (String) pdb.getProperty("protein sequence ID"); if (cdsId != null && cdsId.trim().length() > 0) { dbr = new DBRefEntry(DBRefSource.ENSEMBL, DBRefSource.UNIPROT diff --git a/src/jalview/ws/sifts/SiftsClient.java b/src/jalview/ws/sifts/SiftsClient.java index fb59071..acca50f 100644 --- a/src/jalview/ws/sifts/SiftsClient.java +++ b/src/jalview/ws/sifts/SiftsClient.java @@ -390,8 +390,8 @@ public class SiftsClient implements SiftsClientI String pdbFile, String chain) throws SiftsException { structId = (chain == null) ? pdbId : pdbId + "|" + chain; - System.out.println("Getting mapping for: " + pdbId + "|" + chain - + " : seq- " + seq.getName()); + System.out.println("Getting SIFTS mapping for " + structId + ": seq " + + seq.getName()); final StringBuilder mappingDetails = new StringBuilder(128); PrintStream ps = new PrintStream(System.out) @@ -470,12 +470,13 @@ public class SiftsClient implements SiftsClientI int pdbStart = UNASSIGNED; int pdbEnd = UNASSIGNED; - Integer[] keys = mapping.keySet().toArray(new Integer[0]); - Arrays.sort(keys); - if (keys.length < 1) + if (mapping.isEmpty()) { - throw new SiftsException(">>> Empty SIFTS mapping generated!!"); + throw new SiftsException("SIFTS mapping failed"); } + + Integer[] keys = mapping.keySet().toArray(new Integer[0]); + Arrays.sort(keys); seqStart = keys[0]; seqEnd = keys[keys.length - 1]; diff --git a/test/jalview/datamodel/PDBEntryTest.java b/test/jalview/datamodel/PDBEntryTest.java index 979fee4..e9d5cb2 100644 --- a/test/jalview/datamodel/PDBEntryTest.java +++ b/test/jalview/datamodel/PDBEntryTest.java @@ -32,8 +32,6 @@ import static org.testng.Assert.fail; import jalview.datamodel.PDBEntry.Type; -import java.util.Hashtable; - //import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -101,17 +99,17 @@ public class PDBEntryTest assertNotEquals(case9, case10); // add properties - case7.getProperty().put("hello", "world"); + case7.setProperty("hello", "world"); assertNotEquals(case7, case9); - case9.getProperty().put("hello", "world"); + case9.setProperty("hello", "world"); assertEquals(case7, case9); - case9.getProperty().put("hello", "WORLD"); + case9.setProperty("hello", "WORLD"); assertNotEquals(case7, case9); /* * change string wrapper property to string... */ - case1.getProperty().put("chain_code", "a"); + case1.setProperty("chain_code", "a"); assertFalse(pdbEntry.equals(case1)); assertFalse(case1.equals(pdbEntry)); } @@ -245,28 +243,23 @@ public class PDBEntryTest */ pdb1 = new PDBEntry("3A6S", null, null, null); pdb2 = new PDBEntry("3A6S", null, null, null); - // ughh properties not null if chain code has been set... - // JAL-2196 addresses this - pdb1.properties = new Hashtable(); - pdb2.properties = new Hashtable(); - pdb1.properties.put("destination", "mars"); - pdb1.properties.put("hello", "world"); - pdb2.properties.put("hello", "moon"); - pdb2.properties.put("goodbye", "world"); + pdb1.setProperty("destination", "mars"); + pdb1.setProperty("hello", "world"); + pdb2.setProperty("hello", "moon"); + pdb2.setProperty("goodbye", "world"); assertTrue(pdb1.updateFrom(pdb2)); - assertEquals(pdb1.properties.get("destination"), "mars"); - assertEquals(pdb1.properties.get("hello"), "moon"); - assertEquals(pdb1.properties.get("goodbye"), "world"); + assertEquals(pdb1.getProperty("destination"), "mars"); + assertEquals(pdb1.getProperty("hello"), "moon"); + assertEquals(pdb1.getProperty("goodbye"), "world"); /* * add properties only */ pdb1 = new PDBEntry("3A6S", null, null, null); pdb2 = new PDBEntry("3A6S", null, null, null); - pdb2.properties = new Hashtable(); - pdb2.properties.put("hello", "moon"); + pdb2.setProperty("hello", "moon"); assertTrue(pdb1.updateFrom(pdb2)); - assertEquals(pdb1.properties.get("hello"), "moon"); + assertEquals(pdb1.getProperty("hello"), "moon"); } @Test(groups = { "Functional" }) diff --git a/test/jalview/ext/jmol/JmolParserTest.java b/test/jalview/ext/jmol/JmolParserTest.java index fb092f6..b2d3253 100644 --- a/test/jalview/ext/jmol/JmolParserTest.java +++ b/test/jalview/ext/jmol/JmolParserTest.java @@ -21,6 +21,7 @@ package jalview.ext.jmol; import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; import jalview.bin.Cache; @@ -248,4 +249,30 @@ public class JmolParserTest assertEquals('H', structCode[4]); assertEquals('E', structCode[5]); } + + @Test(groups = "Functional") + public void testLocalPDBId() throws Exception + { + JmolParser structureData; + /* + * reads a local structure + */ + structureData = new JmolParser("examples/testdata/localstruct.pdb", + AppletFormatAdapter.FILE); + assertNotNull(structureData); + /* + * local structure files should yield a false ID based on the filename + */ + assertNotNull(structureData.getId()); + assertEquals(structureData.getId(), "localstruct.pdb"); + assertNotNull(structureData.getSeqs()); + /* + * the ID is also the group for features derived from structure data + */ + assertNotNull(structureData.getSeqs().get(0).getSequenceFeatures()[0].featureGroup); + assertEquals( + structureData.getSeqs().get(0).getSequenceFeatures()[0].featureGroup, + "localstruct.pdb"); + + } } diff --git a/test/jalview/gui/StructureChooserTest.java b/test/jalview/gui/StructureChooserTest.java index 1e41a16..446d32d 100644 --- a/test/jalview/gui/StructureChooserTest.java +++ b/test/jalview/gui/StructureChooserTest.java @@ -28,6 +28,7 @@ import jalview.datamodel.DBRefSource; import jalview.datamodel.PDBEntry; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; +import jalview.jbgui.GStructureChooser.FilterOption; import java.util.Vector; @@ -109,15 +110,21 @@ public class StructureChooserTest { SequenceI[] selectedSeqs = new SequenceI[] { seq }; StructureChooser sc = new StructureChooser(selectedSeqs, seq, null); - sc.populateFilterComboBox(false); + sc.populateFilterComboBox(false, false); int optionsSize = sc.getCmbFilterOption().getItemCount(); assertEquals(3, optionsSize); // if structures are not discovered then don't // populate filter options - sc.populateFilterComboBox(true); + sc.populateFilterComboBox(true, false); optionsSize = sc.getCmbFilterOption().getItemCount(); assertTrue(optionsSize > 3); // if structures are found, filter options // should be populated + + sc.populateFilterComboBox(true, true); + assertTrue(sc.getCmbFilterOption().getSelectedItem() != null); + FilterOption filterOpt = (FilterOption) sc.getCmbFilterOption() + .getSelectedItem(); + assertEquals("Cached PDB Entries", filterOpt.getName()); } @Test(groups = { "Functional" }) diff --git a/test/jalview/gui/StructureViewerTest.java b/test/jalview/gui/StructureViewerTest.java new file mode 100644 index 0000000..f8e9133 --- /dev/null +++ b/test/jalview/gui/StructureViewerTest.java @@ -0,0 +1,33 @@ +package jalview.gui; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; + +import jalview.datamodel.PDBEntry; +import jalview.datamodel.PDBEntry.Type; + +import org.testng.annotations.Test; + +public class StructureViewerTest +{ + @Test(groups = "Functional") + public void testGetUniquePdbFiles() + { + assertNull(StructureViewer.getUniquePdbFiles(null)); + + PDBEntry pdbe1 = new PDBEntry("1A70", "A", Type.PDB, "path1"); + PDBEntry pdbe2 = new PDBEntry("3A6S", "A", Type.PDB, "path2"); + PDBEntry pdbe3 = new PDBEntry("1A70", "B", Type.PDB, "path1"); + PDBEntry pdbe4 = new PDBEntry("1GAQ", "A", Type.PDB, null); + PDBEntry pdbe5 = new PDBEntry("3A6S", "B", Type.PDB, "path2"); + PDBEntry pdbe6 = new PDBEntry("1GAQ", "B", Type.PDB, null); + + /* + * pdbe2 and pdbe5 get removed as having a duplicate file path + */ + PDBEntry[] uniques = StructureViewer.getUniquePdbFiles(new PDBEntry[] { + pdbe1, pdbe2, pdbe3, pdbe4, pdbe5, pdbe6 }); + assertEquals(uniques, + new PDBEntry[] { pdbe1, pdbe2, pdbe4, pdbe6 }); + } +} diff --git a/test/jalview/structures/models/AAStructureBindingModelTest.java b/test/jalview/structures/models/AAStructureBindingModelTest.java index bb81992..0d00169 100644 --- a/test/jalview/structures/models/AAStructureBindingModelTest.java +++ b/test/jalview/structures/models/AAStructureBindingModelTest.java @@ -49,26 +49,36 @@ import org.testng.annotations.Test; */ public class AAStructureBindingModelTest { + /* + * Scenario: Jalview has 4 sequences, corresponding to 1YCS (chains A and B), 3A6S|B, 1OOT|A + */ private static final String PDB_1 = "HEADER COMPLEX (ANTI-ONCOGENE/ANKYRIN REPEATS) 30-SEP-96 1YCS \n" + "ATOM 2 CA VAL A 97 24.134 4.926 45.821 1.00 47.43 C \n" + "ATOM 9 CA PRO A 98 25.135 8.584 46.217 1.00 41.60 C \n" + "ATOM 16 CA SER A 99 28.243 9.596 44.271 1.00 39.63 C \n" + "ATOM 22 CA GLN A 100 31.488 10.133 46.156 1.00 35.60 C \n" - + "ATOM 31 CA LYS A 101 33.323 11.587 43.115 1.00 41.69 C \n"; + // artificial jump in residue numbering to prove it is correctly + // mapped: + + "ATOM 31 CA LYS A 102 33.323 11.587 43.115 1.00 41.69 C \n" + + "ATOM 1857 CA GLU B 374 9.193 -16.005 95.870 1.00 54.22 C \n" + + "ATOM 1866 CA ILE B 375 7.101 -14.921 92.847 1.00 46.82 C \n" + + "ATOM 1874 CA VAL B 376 10.251 -13.625 91.155 1.00 47.80 C \n" + + "ATOM 1881 CA LYS B 377 11.767 -17.068 91.763 1.00 50.21 C \n" + + "ATOM 1890 CA PHE B 378 8.665 -18.948 90.632 1.00 44.85 C \n"; private static final String PDB_2 = "HEADER HYDROLASE 09-SEP-09 3A6S \n" - + "ATOM 2 CA MET A 1 15.366 -11.648 24.854 1.00 32.05 C \n" - + "ATOM 10 CA LYS A 2 16.846 -9.215 22.340 1.00 25.68 C \n" - + "ATOM 19 CA LYS A 3 15.412 -6.335 20.343 1.00 19.42 C \n" - + "ATOM 28 CA LEU A 4 15.629 -5.719 16.616 1.00 15.49 C \n" - + "ATOM 36 CA GLN A 5 14.412 -2.295 15.567 1.00 12.19 C \n"; + + "ATOM 2 CA MET B 1 15.366 -11.648 24.854 1.00 32.05 C \n" + + "ATOM 10 CA LYS B 2 16.846 -9.215 22.340 1.00 25.68 C \n" + + "ATOM 19 CA LYS B 3 15.412 -6.335 20.343 1.00 19.42 C \n" + + "ATOM 28 CA LEU B 4 15.629 -5.719 16.616 1.00 15.49 C \n" + + "ATOM 36 CA GLN B 5 14.412 -2.295 15.567 1.00 12.19 C \n"; private static final String PDB_3 = "HEADER STRUCTURAL GENOMICS 04-MAR-03 1OOT \n" - + "ATOM 2 CA SER A 1 29.427 3.330 -6.578 1.00 32.50 C \n" - + "ATOM 8 CA PRO A 2 29.975 3.340 -2.797 1.00 17.62 C \n" - + "ATOM 16 CA ALYS A 3 26.958 3.024 -0.410 0.50 8.78 C \n" - + "ATOM 33 CA ALA A 4 26.790 4.320 3.172 1.00 11.98 C \n" - + "ATOM 39 CA AVAL A 5 24.424 3.853 6.106 0.50 13.83 C \n"; + + "ATOM 2 CA SER A 7 29.427 3.330 -6.578 1.00 32.50 C \n" + + "ATOM 8 CA PRO A 8 29.975 3.340 -2.797 1.00 17.62 C \n" + + "ATOM 16 CA ALYS A 9 26.958 3.024 -0.410 0.50 8.78 C \n" + + "ATOM 33 CA ALA A 10 26.790 4.320 3.172 1.00 11.98 C \n" + + "ATOM 39 CA AVAL A 12 24.424 3.853 6.106 0.50 13.83 C \n"; AAStructureBindingModel testee; @@ -80,24 +90,28 @@ public class AAStructureBindingModelTest @BeforeMethod(alwaysRun = true) public void setUp() { - SequenceI seq1 = new Sequence("1YCS", "-VPSQK"); + SequenceI seq1a = new Sequence("1YCS|A", "-VPSQK"); + SequenceI seq1b = new Sequence("1YCS|B", "EIVKF-"); SequenceI seq2 = new Sequence("3A6S", "MK-KLQ"); SequenceI seq3 = new Sequence("1OOT", "SPK-AV"); - al = new Alignment(new SequenceI[] { seq1, seq2, seq3 }); + al = new Alignment(new SequenceI[] { seq1a, seq1b, seq2, seq3 }); al.setDataset(null); + /* + * give pdb files the name generated by Jalview for PASTE source + */ PDBEntry[] pdbFiles = new PDBEntry[3]; - pdbFiles[0] = new PDBEntry("1YCS", "A", Type.PDB, "1YCS.pdb"); - pdbFiles[1] = new PDBEntry("3A6S", "B", Type.PDB, "3A6S.pdb"); - pdbFiles[2] = new PDBEntry("1OOT", "A", Type.PDB, "1OOT.pdb"); + pdbFiles[0] = new PDBEntry("1YCS", "A", Type.PDB, "INLINE1YCS"); + pdbFiles[1] = new PDBEntry("3A6S", "B", Type.PDB, "INLINE3A6S"); + pdbFiles[2] = new PDBEntry("1OOT", "A", Type.PDB, "INLINE1OOT"); String[][] chains = new String[3][]; SequenceI[][] seqs = new SequenceI[3][]; - seqs[0] = new SequenceI[] { seq1 }; + seqs[0] = new SequenceI[] { seq1a, seq1b }; seqs[1] = new SequenceI[] { seq2 }; seqs[2] = new SequenceI[] { seq3 }; StructureSelectionManager ssm = new StructureSelectionManager(); - ssm.setMapping(new SequenceI[] { seq1 }, null, PDB_1, + ssm.setMapping(new SequenceI[] { seq1a, seq1b }, null, PDB_1, AppletFormatAdapter.PASTE); ssm.setMapping(new SequenceI[] { seq2 }, null, PDB_2, AppletFormatAdapter.PASTE); @@ -109,10 +123,6 @@ public class AAStructureBindingModelTest @Override public String[] getPdbFile() { - /* - * fudge 'filenames' to match those generated when PDBFile parses PASTE - * data - */ return new String[] { "INLINE1YCS", "INLINE3A6S", "INLINE1OOT" }; } @@ -140,7 +150,10 @@ public class AAStructureBindingModelTest @Test(groups = { "Functional" }) public void testFindSuperposableResidues() { - SuperposeData[] structs = new SuperposeData[al.getHeight()]; + /* + * create a data bean to hold data per structure file + */ + SuperposeData[] structs = new SuperposeData[testee.getPdbFile().length]; for (int i = 0; i < structs.length; i++) { structs[i] = testee.new SuperposeData(al.getWidth()); @@ -162,10 +175,23 @@ public class AAStructureBindingModelTest */ assertFalse(matched[0]); // gap in first sequence assertTrue(matched[1]); - assertFalse(matched[2]); // gap in second sequence - assertFalse(matched[3]); // gap in third sequence + assertFalse(matched[2]); // gap in third sequence + assertFalse(matched[3]); // gap in fourth sequence assertTrue(matched[4]); - assertTrue(matched[5]); + assertTrue(matched[5]); // gap in second sequence + + assertEquals("1YCS", structs[0].pdbId); + assertEquals("3A6S", structs[1].pdbId); + assertEquals("1OOT", structs[2].pdbId); + assertEquals("A", structs[0].chain); // ? struct has chains A _and_ B + assertEquals("B", structs[1].chain); + assertEquals("A", structs[2].chain); + // the 0's for unsuperposable positions propagate down the columns: + assertEquals("[0, 97, 98, 99, 100, 102]", + Arrays.toString(structs[0].pdbResNo)); + assertEquals("[0, 2, 0, 3, 4, 5]", Arrays.toString(structs[1].pdbResNo)); + assertEquals("[0, 8, 0, 0, 10, 12]", + Arrays.toString(structs[2].pdbResNo)); } @Test(groups = { "Functional" }) diff --git a/test/jalview/ws/dbsources/UniprotTest.java b/test/jalview/ws/dbsources/UniprotTest.java index 57980b8..2df8be6 100644 --- a/test/jalview/ws/dbsources/UniprotTest.java +++ b/test/jalview/ws/dbsources/UniprotTest.java @@ -21,6 +21,7 @@ package jalview.ws.dbsources; import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertNull; @@ -117,25 +118,21 @@ public class UniprotTest PDBEntry xref = xrefs.get(0); assertEquals("2FSQ", xref.getId()); assertEquals("PDB", xref.getType()); - assertEquals(2, xref.getProperty().size()); - assertEquals("X-ray", xref.getProperty().get("method")); - assertEquals("1.40", xref.getProperty().get("resolution")); + assertEquals("X-ray", xref.getProperty("method")); + assertEquals("1.40", xref.getProperty("resolution")); xref = xrefs.get(1); assertEquals("2FSR", xref.getId()); assertEquals("PDBsum", xref.getType()); - assertNull(xref.getProperty()); + assertFalse(xref.getProperties().hasMoreElements()); xref = xrefs.get(2); assertEquals("AE007869", xref.getId()); assertEquals("EMBL", xref.getType()); - assertNotNull(xref.getProperty()); assertEquals("AAK85932.1", - (String) xref.getProperty().get("protein sequence ID")); + xref.getProperty("protein sequence ID")); assertEquals("Genomic_DNA", - (String) xref.getProperty().get("molecule type")); - assertEquals(2, xref.getProperty().size()); - + xref.getProperty("molecule type")); } @Test(groups = { "Functional" }) diff --git a/utils/BufferedLineReader.java b/utils/BufferedLineReader.java new file mode 100644 index 0000000..b813fb2 --- /dev/null +++ b/utils/BufferedLineReader.java @@ -0,0 +1,182 @@ +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.StringReader; + +/** + * A file reader that concatenates lines + * + * @author gmcarstairs + * + */ +public class BufferedLineReader +{ + interface LineCleaner + { + String cleanLine(String l); + } + + /* + * a reader for the file being read + */ + private BufferedReader br; + + /* + * optional handler to post-process each line as it is read + */ + private LineCleaner cleaner; + + /* + * current buffer of post-processed input lines + */ + private String[] buffer; + + private boolean atEof; + + /** + * Constructor + * + * @param reader + * @param bufferSize + * the number of lines to concatenate at a time while reading + * @param tidier + * an optional callback handler to post-process each line after + * reading + * @throws FileNotFoundException + */ + public BufferedLineReader(BufferedReader reader, int bufferSize, + LineCleaner tidier) + throws IOException + { + br = reader; + buffer = new String[bufferSize]; + cleaner = tidier; + + /* + * load up the buffer with N-1 lines, ready for the first read + */ + for (int i = 1; i < bufferSize; i++) + { + readLine(); + } + + } + + /** + * Reads the next line from file, invokes the post-processor if one was + * provided, and returns the 'cleaned' line, or null at end of file. + * + * @return + */ + private String readLine() // throws IOException + { + if (atEof) + { + return null; + } + + String line = null; + try + { + line = br.readLine(); + } catch (IOException e) + { + e.printStackTrace(); + } + if (line == null) + { + atEof = true; + return null; + } + if (cleaner != null) + { + line = cleaner.cleanLine(line); + } + + /* + * shuffle down the lines buffer and add the new line + * in the last position + */ + for (int i = 1; i < buffer.length; i++) + { + buffer[i - 1] = buffer[i]; + } + buffer[buffer.length - 1] = line; + return line; + } + + /** + * Returns a number of concatenated lines from the file, or null at end of + * file. + * + * @return + */ + public String read() + { + if (readLine() == null) + { + return null; + } + StringBuilder result = new StringBuilder(100 * buffer.length); + for (String line : buffer) + { + if (line != null) + { + result.append(line); + } + } + return result.toString(); + } + + /** + * A main 'test' method! + * + * @throws IOException + */ + public static void main(String[] args) throws IOException + { + String data = "Now is the winter\n" + "Of our discontent\n" + + "Made glorious summer\n" + "By this sun of York\n"; + BufferedReader br = new BufferedReader(new StringReader(data)); + BufferedLineReader reader = new BufferedLineReader(br, 3, + new LineCleaner() + { + @Override + public String cleanLine(String l) + { + return l.toUpperCase(); + } + }); + String line = reader.read(); + String expect = "NOW IS THE WINTEROF OUR DISCONTENTMADE GLORIOUS SUMMER"; + if (!line.equals(expect)) + { + System.err.println("Fail: expected '" + expect + "', found '" + line + + ";"); + } + else + { + System.out.println("Line one ok!"); + } + line = reader.read(); + expect = "OF OUR DISCONTENTMADE GLORIOUS SUMMERBY THIS SUN OF YORK"; + if (!line.equals(expect)) + { + System.err.println("Fail: expected '" + expect + "', found '" + line + + "'"); + } + else + { + System.out.println("Line two ok!!"); + } + line = reader.read(); + if (line != null) + { + System.err.println("Fail: expected null at eof, got '" + line + "'"); + } + else + { + System.out.println("EOF ok!!!"); + } + } +} diff --git a/utils/HelpLinksChecker.java b/utils/HelpLinksChecker.java index 7c57cc7..1279b31 100644 --- a/utils/HelpLinksChecker.java +++ b/utils/HelpLinksChecker.java @@ -34,7 +34,7 @@ import java.util.Map; * @author gmcarstairs * */ -public class HelpLinksChecker +public class HelpLinksChecker implements BufferedLineReader.LineCleaner { private static final String HELP_HS = "help.hs"; @@ -358,7 +358,8 @@ public class HelpLinksChecker try { BufferedReader br = new BufferedReader(new FileReader(hrefFile)); - String data = br.readLine(); + BufferedLineReader blr = new BufferedLineReader(br, 3, this); + String data = blr.read(); while (data != null) { if (data.contains(nameAnchor) || data.contains(idAnchor)) @@ -366,7 +367,7 @@ public class HelpLinksChecker found = true; break; } - data = br.readLine(); + data = blr.read(); } br.close(); } catch (IOException e) @@ -544,4 +545,14 @@ public class HelpLinksChecker } return value; } + + /** + * Trim whitespace from concatenated lines but preserve one space for valid + * parsing + */ + @Override + public String cleanLine(String l) + { + return l.trim() + " "; + } } diff --git a/utils/MessageBundleChecker.java b/utils/MessageBundleChecker.java index 15e16cf..4489a93 100644 --- a/utils/MessageBundleChecker.java +++ b/utils/MessageBundleChecker.java @@ -40,7 +40,7 @@ import java.util.regex.Pattern; * @author gmcarstairs * */ -public class MessageBundleChecker +public class MessageBundleChecker implements BufferedLineReader.LineCleaner { /* * regex ^"[^"]*"$ @@ -212,64 +212,37 @@ public class MessageBundleChecker return; } - String[] lines = new String[bufferSize]; BufferedReader br = new BufferedReader(new FileReader(f)); - for (int i = 0; i < bufferSize; i++) - { - String readLine = br.readLine(); - lines[i] = stripCommentsAndTrim(readLine); - } + BufferedLineReader blr = new BufferedLineReader(br, bufferSize, this); int lineNo = 0; - - while (lines[bufferSize - 1] != null) + String line = blr.read(); + while (line != null) { lineNo++; - inspectSourceLines(path, lineNo, lines); - - for (int i = 0; i < bufferSize - 1; i++) - { - lines[i] = lines[i + 1]; - } - lines[bufferSize - 1] = stripCommentsAndTrim(br.readLine()); + inspectSourceLines(path, lineNo, line); + line = blr.read(); } br.close(); } - /* - * removes anything after (and including) '//' - */ - private String stripCommentsAndTrim(String line) - { - if (line != null) - { - int pos = line.indexOf("//"); - if (pos != -1) - { - line = line.substring(0, pos); - } - line = line.replace("\t", " ").trim(); - } - return line; - } - /** * Look for calls to MessageManager methods, possibly split over two or more - * lines + * lines that have been concatenated while parsing the file * * @param path * @param lineNo - * @param lines + * @param line */ - private void inspectSourceLines(String path, int lineNo, String[] lines) + private void inspectSourceLines(String path, int lineNo, String line) { - String lineNos = String.format("%d-%d", lineNo, lineNo + lines.length + String lineNos = String + .format("%d-%d", lineNo, lineNo + bufferSize - 1); - String combined = combineLines(lines); for (String method : METHODS) { - int pos = combined.indexOf(method); + int pos = line.indexOf(method); if (pos == -1) { continue; @@ -278,7 +251,7 @@ public class MessageBundleChecker /* * extract what follows the opening bracket of the method call */ - String methodArgs = combined.substring(pos + method.length()).trim(); + String methodArgs = line.substring(pos + method.length()).trim(); if ("".equals(methodArgs)) { /* @@ -305,7 +278,7 @@ public class MessageBundleChecker if (METHOD3 == method) { System.out.println(String.format("Dynamic key at %s line %s %s", - path.substring(sourcePath.length()), lineNos, combined)); + path.substring(sourcePath.length()), lineNos, line)); continue; } @@ -313,14 +286,14 @@ public class MessageBundleChecker if (messageKey == null) { System.out.println(String.format("Trouble parsing %s line %s %s", - path.substring(sourcePath.length()), lineNos, combined)); + path.substring(sourcePath.length()), lineNos, line)); continue; } if (!(STRING_PATTERN.matcher(messageKey).matches())) { System.out.println(String.format("Dynamic key at %s line %s %s", - path.substring(sourcePath.length()), lineNos, combined)); + path.substring(sourcePath.length()), lineNos, line)); continue; } @@ -384,22 +357,6 @@ public class MessageBundleChecker return endPos == -1 ? null : key.substring(0, endPos); } - private String combineLines(String[] lines) - { - String combined = ""; - if (lines != null) - { - for (String line : lines) - { - if (line != null) - { - combined += line; - } - } - } - return combined; - } - /** * Loads properties from Message.properties * @@ -421,4 +378,22 @@ public class MessageBundleChecker } + /** + * Remove any trailing comments, change tabs to space, and trim + */ + @Override + public String cleanLine(String l) + { + if (l != null) + { + int pos = l.indexOf("//"); + if (pos != -1) + { + l = l.substring(0, pos); + } + l = l.replace("\t", " ").trim(); + } + return l; + } + }