diff --git a/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/ecod/EcodInstallationTest.java b/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/ecod/EcodInstallationTest.java
index 8ab3f29fb3..f384f2ba65 100644
--- a/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/ecod/EcodInstallationTest.java
+++ b/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/ecod/EcodInstallationTest.java
@@ -22,8 +22,12 @@
import static org.junit.Assert.*;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileReader;
import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -47,6 +51,7 @@
import org.biojava.nbio.structure.ecod.EcodDomain;
import org.biojava.nbio.structure.ecod.EcodFactory;
import org.biojava.nbio.structure.ecod.EcodInstallation;
+import org.biojava.nbio.structure.ecod.EcodInstallation.EcodParser;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -277,12 +282,47 @@ public void testFilterByHierarchy() throws IOException {
assertEquals(expected,actual);
}
+ /**
+ * Checks that the current release can still be read.
+ *
+ * The version is read from the file's header without parsing the domains, so this
+ * additionally parses the first few thousand lines of the same file. That is enough to
+ * notice a column change — which is what ECOD did at v294.1, unnoticed for months —
+ * without building the three million domains the whole file now holds.
+ */
@Test
public void testVersion() throws IOException {
EcodDatabase ecod3 = EcodFactory.getEcodDatabase("latest");
String version = ecod3.getVersion();
assertNotNull(version);
assertNotEquals("latest", version);
+ System.out.println("latest version of ECOD is "+version);
+
+ File domainsFile = new File(((EcodInstallation) ecod3).getCacheLocation(),
+ "ecod.latest.domains.txt");
+ assertTrue("No local copy of the domains file at "+domainsFile, domainsFile.exists());
+
+ EcodParser parser = new EcodParser(firstLines(domainsFile, 5000));
+ assertEquals(version, parser.getVersion());
+ assertFalse("No domains parsed from ECOD "+version
+ + "; the distribution format has probably changed",
+ parser.getDomains().isEmpty());
+ }
+
+ /**
+ * @return a reader over the first {@code maxLines} lines of the file
+ */
+ private static Reader firstLines(File f, int maxLines) throws IOException {
+ StringBuilder head = new StringBuilder();
+ try (BufferedReader in = new BufferedReader(new FileReader(f))) {
+ String line;
+ int n = 0;
+ while (n < maxLines && (line = in.readLine()) != null) {
+ head.append(line).append('\n');
+ n++;
+ }
+ }
+ return new StringReader(head.toString());
}
/**
diff --git a/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/io/TestSeqResParsing.java b/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/io/TestSeqResParsing.java
index de6c072719..6ea23aef8a 100644
--- a/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/io/TestSeqResParsing.java
+++ b/biojava-integrationtest/src/test/java/org/biojava/nbio/structure/test/io/TestSeqResParsing.java
@@ -53,7 +53,7 @@ public void test11GS() throws IOException, StructureException{
s = StructureIO.getStructure(pdbID);
assertNotNull(s);
- assertTrue(s.getChains().size() > 0);
+ assertFalse(s.getChains().isEmpty());
Chain c = s.getChainByIndex(0);
assertTrue(c.getSeqResGroups().size() > 2);
diff --git a/biojava-modfinder/pom.xml b/biojava-modfinder/pom.xml
index 0b8cb53791..e0db50f1db 100644
--- a/biojava-modfinder/pom.xml
+++ b/biojava-modfinder/pom.xml
@@ -4,7 +4,7 @@
biojava
org.biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava-modfinder
biojava-modfinder
@@ -31,7 +31,7 @@
org.biojava
biojava-structure
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
jar
compile
diff --git a/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/io/ModifiedCompoundXMLConverter.java b/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/io/ModifiedCompoundXMLConverter.java
index 187e113924..e038f57cd3 100644
--- a/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/io/ModifiedCompoundXMLConverter.java
+++ b/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/io/ModifiedCompoundXMLConverter.java
@@ -69,7 +69,7 @@ public static String toXML(ModifiedCompound mc) throws IOException{
Set linkages = mc.getAtomLinkages();
- if ( linkages.size() > 0 ) {
+ if (!linkages.isEmpty()) {
int pos = -1;
for ( StructureAtomLinkage link: linkages){
pos ++;
diff --git a/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/structure/ProteinModificationIdentifier.java b/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/structure/ProteinModificationIdentifier.java
index c9575a5444..0d94d36e6e 100644
--- a/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/structure/ProteinModificationIdentifier.java
+++ b/biojava-modfinder/src/main/java/org/biojava/nbio/protmod/structure/ProteinModificationIdentifier.java
@@ -285,7 +285,7 @@ public void identify(final List chains,
if (residues.isEmpty()) {
String pdbId = "?";
- if ( chains.size() > 0) {
+ if (!chains.isEmpty()) {
Structure struc = chains.get(0).getStructure();
if ( struc != null)
pdbId = struc.getPDBCode();
diff --git a/biojava-modfinder/src/test/java/org/biojava/nbio/protmod/phosphosite/TestAcetylation.java b/biojava-modfinder/src/test/java/org/biojava/nbio/protmod/phosphosite/TestAcetylation.java
index 34376307a0..ba8e6d2a3d 100644
--- a/biojava-modfinder/src/test/java/org/biojava/nbio/protmod/phosphosite/TestAcetylation.java
+++ b/biojava-modfinder/src/test/java/org/biojava/nbio/protmod/phosphosite/TestAcetylation.java
@@ -32,7 +32,8 @@
import java.net.URL;
import java.util.List;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -100,11 +101,11 @@ public void testAcetylation() throws IOException {
List sites = Site.parseSites(localFile);
- assertTrue(sites.size() > 0);
+ assertFalse(sites.isEmpty());
for (Site s : sites) {
- assertTrue(s.getResidue() != null);
+ assertNotNull(s.getResidue());
}
diff --git a/biojava-ontology/pom.xml b/biojava-ontology/pom.xml
index 001e3e52f1..eb9e3564c1 100644
--- a/biojava-ontology/pom.xml
+++ b/biojava-ontology/pom.xml
@@ -4,7 +4,7 @@
org.biojava
biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava-ontology
diff --git a/biojava-ontology/src/main/java/org/biojava/nbio/ontology/utils/WeakValueHashMap.java b/biojava-ontology/src/main/java/org/biojava/nbio/ontology/utils/WeakValueHashMap.java
index 7762642c76..c508cdc206 100644
--- a/biojava-ontology/src/main/java/org/biojava/nbio/ontology/utils/WeakValueHashMap.java
+++ b/biojava-ontology/src/main/java/org/biojava/nbio/ontology/utils/WeakValueHashMap.java
@@ -58,12 +58,12 @@ public WeakValueHashMap() {
private void diddleReferenceQueue() {
// Avoid making behind-the-scenes modifications while iterators exist.
- if (iteratorRefs.size() > 0) {
+ if (!iteratorRefs.isEmpty()) {
Reference ref;
while ((ref = iteratorRefQueue.poll()) != null) {
iteratorRefs.remove(ref);
}
- if (iteratorRefs.size() > 0) {
+ if (!iteratorRefs.isEmpty()) {
return;
}
}
diff --git a/biojava-protein-comparison-tool/pom.xml b/biojava-protein-comparison-tool/pom.xml
index 12e4941d55..1527d691f4 100644
--- a/biojava-protein-comparison-tool/pom.xml
+++ b/biojava-protein-comparison-tool/pom.xml
@@ -4,7 +4,7 @@
biojava
org.biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava-protein-comparison-tool
@@ -36,23 +36,23 @@
org.biojava
biojava-alignment
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
org.biojava
biojava-core
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
org.biojava
biojava-structure
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
org.biojava
biojava-structure-gui
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
net.sourceforge.jmol
diff --git a/biojava-protein-disorder/pom.xml b/biojava-protein-disorder/pom.xml
index 82107e8058..827c708a2b 100644
--- a/biojava-protein-disorder/pom.xml
+++ b/biojava-protein-disorder/pom.xml
@@ -3,7 +3,7 @@
biojava
org.biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava-protein-disorder
jar
@@ -63,7 +63,7 @@
org.biojava
biojava-core
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
diff --git a/biojava-structure-gui/pom.xml b/biojava-structure-gui/pom.xml
index 24644c8ffe..21e01ce42e 100644
--- a/biojava-structure-gui/pom.xml
+++ b/biojava-structure-gui/pom.xml
@@ -3,7 +3,7 @@
biojava
org.biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
4.0.0
biojava-structure-gui
@@ -27,13 +27,13 @@
org.biojava
biojava-structure
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
compile
org.biojava
biojava-core
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
compile
diff --git a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/JAutoSuggest.java b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/JAutoSuggest.java
index 9e3c825910..c5542b4edc 100644
--- a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/JAutoSuggest.java
+++ b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/JAutoSuggest.java
@@ -229,7 +229,7 @@ public void keyReleased(KeyEvent e) {
list.ensureIndexIsVisible(list.getSelectedIndex() - 1);
return;
} else if (e.getKeyCode() == KeyEvent.VK_ENTER
- && list.getSelectedIndex() != -1 && suggestions.size() > 0) {
+ && list.getSelectedIndex() != -1 && !suggestions.isEmpty()) {
setText((String) list.getSelectedValue());
@@ -365,7 +365,7 @@ public String doInBackground() {
setFont(regular);
- if (suggestions.size() > 0) {
+ if (!suggestions.isEmpty()) {
list.setListData(suggestions);
list.setSelectedIndex(0);
list.ensureIndexIsVisible(0);
diff --git a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/SCOPAutoSuggestProvider.java b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/SCOPAutoSuggestProvider.java
index 136f184584..8e1a975205 100644
--- a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/SCOPAutoSuggestProvider.java
+++ b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/autosuggest/SCOPAutoSuggestProvider.java
@@ -111,7 +111,7 @@ private List getPossibleScopDomains(String userInput) {
if ( stop.get())
return domains;
- if ( domains == null || domains.size() < 1){
+ if ( domains == null || domains.isEmpty()){
if ( userInput.length() > 5){
// e.g. d4hhba
@@ -127,11 +127,11 @@ private List getPossibleScopDomains(String userInput) {
if (DEBUG)
System.out.println("domains: " + domains);
- if ( domains == null || domains.size() < 1) {
+ if ( domains == null || domains.isEmpty()) {
if ( userInput.length() > 0 ){
List descs = scop.filterByClassificationId(userInput);
- if ( descs == null || descs.size() < 1){
+ if ( descs == null || descs.isEmpty()){
descs = scop.filterByDescription(userInput);
}
diff --git a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/jmol/RasmolCommandListener.java b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/jmol/RasmolCommandListener.java
index d1ce5c0e30..feec8b0366 100644
--- a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/jmol/RasmolCommandListener.java
+++ b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/align/gui/jmol/RasmolCommandListener.java
@@ -74,7 +74,7 @@ public void actionPerformed(ActionEvent event) {
// check last command in history
// if equivalent, don't add,
// otherwise add
- if (history.size()>0){
+ if (!history.isEmpty()){
String txt=history.get(history.size()-1);
if (! txt.equals(cmd)) {
history.add(cmd);
diff --git a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/gui/util/SequenceScalePanel.java b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/gui/util/SequenceScalePanel.java
index 06542e5271..75da6c8e28 100644
--- a/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/gui/util/SequenceScalePanel.java
+++ b/biojava-structure-gui/src/main/java/org/biojava/nbio/structure/gui/util/SequenceScalePanel.java
@@ -126,7 +126,7 @@ private void setPrefSize() {
public void setAligMap(List apos){
this.apos = apos;
- if ( apos.size() == 0)
+ if (apos.isEmpty())
return;
AlignedPosition last = apos.get(apos.size()-1);
diff --git a/biojava-structure/pom.xml b/biojava-structure/pom.xml
index 5392798310..647ab49c72 100644
--- a/biojava-structure/pom.xml
+++ b/biojava-structure/pom.xml
@@ -4,7 +4,7 @@
biojava
org.biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava-structure
biojava-structure
@@ -51,13 +51,13 @@
org.biojava
biojava-alignment
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
compile
org.biojava
biojava-core
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
compile
@@ -87,7 +87,7 @@
com.fasterxml.jackson.core
jackson-databind
- 2.13.4.2
+ 2.18.9
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/Author.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/Author.java
index b0d7253507..bd5a01b885 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/Author.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/Author.java
@@ -62,7 +62,7 @@ public boolean equals(Object obj) {
if ((this.surname == null) ? (other.surname != null) : !this.surname.equals(other.surname)) {
return false;
}
- return !((this.initials == null) ? (other.initials != null) : !this.initials.equals(other.initials));
+ return (this.initials == null) ? other.initials == null : this.initials.equals(other.initials);
}
@Override
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/Element.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/Element.java
index 2f534b2828..4e2d3e340a 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/Element.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/Element.java
@@ -424,7 +424,7 @@ public boolean isHeavyAtom() {
* @return true if Element is not Hydrogen and not Carbon.
*/
public boolean isHeteroAtom() {
- return !(this == C || this == H);
+ return this != C && this != H;
}
/**
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/Site.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/Site.java
index 9158906d23..341483f31b 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/Site.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/Site.java
@@ -83,7 +83,7 @@ public String toPDB() {
@Override
public void toPDB(StringBuffer buf) {
- if (groups == null || groups.size() < 1) {
+ if (groups == null || groups.isEmpty()) {
return;
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ClusterAltAligs.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ClusterAltAligs.java
index 373bcf1611..0933198d7b 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ClusterAltAligs.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ClusterAltAligs.java
@@ -102,7 +102,7 @@ public static void cluster(AlternativeAlignment[] aligs, int cutoff){
}
clusters.add(currentCluster);
- if ( remainList.size() == 0) {
+ if ( remainList.isEmpty()) {
break;
}
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CECalculator.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CECalculator.java
index 6c045ba48e..83f16b7982 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CECalculator.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CECalculator.java
@@ -1450,7 +1450,7 @@ private int optimizeSuperposition(AFPChain afpChain, int nse1, int nse2, int str
//afpChain.setTotalRmsdOpt(rmsd);
//System.out.println("rmsd: " + rmsd);
- if(!(nAtom= strLen * 0.95 && !isRmsdLenAssigned) {
rmsdLen=rmsd;
isRmsdLenAssigned=true;
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CeCalculatorEnhanced.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CeCalculatorEnhanced.java
index 4f57161268..cab98b0113 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CeCalculatorEnhanced.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/ce/CeCalculatorEnhanced.java
@@ -1455,7 +1455,7 @@ private int optimizeSuperposition(AFPChain afpChain, int nse1, int nse2, int str
//afpChain.setTotalRmsdOpt(rmsd);
//System.out.println("rmsd: " + rmsd);
- if(!(nAtom= strLen * 0.95 && !isRmsdLenAssigned) {
rmsdLen=rmsd;
isRmsdLenAssigned=true;
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockImpl.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockImpl.java
index e0423b6f8f..43da1d7c06 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockImpl.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockImpl.java
@@ -127,7 +127,7 @@ public void setAlignRes(List> alignRes) {
public int length() {
if (alignRes == null)
return 0;
- if (alignRes.size() == 0)
+ if (alignRes.isEmpty())
return 0;
return alignRes.get(0).size();
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockSetImpl.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockSetImpl.java
index cbbb3ae895..344ee3c239 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockSetImpl.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/BlockSetImpl.java
@@ -179,7 +179,7 @@ public int size() {
// Get the size from the variables that can contain the information
if (parent != null)
return parent.size();
- else if (getBlocks().size() == 0) {
+ else if (getBlocks().isEmpty()) {
throw new IndexOutOfBoundsException(
"Empty BlockSet: number of Blocks == 0.");
} else
@@ -194,7 +194,7 @@ public int getCoreLength() {
}
protected void updateLength() {
- if (getBlocks().size() == 0) {
+ if (getBlocks().isEmpty()) {
throw new IndexOutOfBoundsException(
"Empty BlockSet: number of Blocks == 0.");
}
@@ -207,7 +207,7 @@ protected void updateLength() {
}
protected void updateCoreLength() {
- if (getBlocks().size() == 0) {
+ if (getBlocks().isEmpty()) {
throw new IndexOutOfBoundsException(
"Empty BlockSet: number of Blocks == 0.");
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/MultipleAlignmentImpl.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/MultipleAlignmentImpl.java
index 738eee30c5..06c93a4403 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/MultipleAlignmentImpl.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/MultipleAlignmentImpl.java
@@ -207,7 +207,7 @@ public int getCoreLength() {
* lengths.
*/
protected void updateLength() {
- if (getBlockSets().size() == 0) {
+ if (getBlockSets().isEmpty()) {
throw new IndexOutOfBoundsException(
"Empty MultipleAlignment: blockSets size == 0.");
} // Otherwise try to calculate it from the BlockSet information
@@ -223,7 +223,7 @@ protected void updateLength() {
* BlockSet core lengths.
*/
protected void updateCoreLength() {
- if (getBlockSets().size() == 0) {
+ if (getBlockSets().isEmpty()) {
throw new IndexOutOfBoundsException(
"Empty MultipleAlignment: blockSets size == 0.");
} // Otherwise try to calculate it from the BlockSet information
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/mc/MultipleMcOptimizer.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/mc/MultipleMcOptimizer.java
index 052f147fc6..29c7012801 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/mc/MultipleMcOptimizer.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/mc/MultipleMcOptimizer.java
@@ -153,7 +153,7 @@ public MultipleMcOptimizer(MultipleAlignment seedAln,
for (Block b : toDelete) {
for (BlockSet bs : msa.getBlockSets()) {
bs.getBlocks().remove(b);
- if (bs.getBlocks().size() == 0)
+ if (bs.getBlocks().isEmpty())
emptyBs.add(bs);
}
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/util/MultipleAlignmentWriter.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/util/MultipleAlignmentWriter.java
index 771b8b5f68..5033576df0 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/util/MultipleAlignmentWriter.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/multiple/util/MultipleAlignmentWriter.java
@@ -205,7 +205,7 @@ public static String toTransformMatrices(MultipleAlignment alignment) {
List btransforms = alignment.getBlockSet(bs)
.getTransformations();
- if (btransforms == null || btransforms.size() < 1)
+ if (btransforms == null || btransforms.isEmpty())
continue;
if (alignment.getBlockSets().size() > 1) {
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/quaternary/QsAlignResult.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/quaternary/QsAlignResult.java
index 7ac77a602e..fe1c9c411b 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/quaternary/QsAlignResult.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/quaternary/QsAlignResult.java
@@ -117,7 +117,7 @@ public void setSubunitMap(Map subunitMap) {
"Subunit Map index higher than Subunit List size.");
// Update the relation enum
- if (subunitMap.size() == 0) {
+ if (subunitMap.isEmpty()) {
relation = QsRelation.DIFFERENT;
} else if (subunitMap.keySet().size() == subunits1.size()) {
if (subunitMap.values().size() == subunits2.size()) {
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AlignmentTools.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AlignmentTools.java
index c6791f4ed2..e535a87508 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AlignmentTools.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AlignmentTools.java
@@ -1313,7 +1313,7 @@ public static Group[] prepareGroupsForDisplay(AFPChain afpChain, Atom[] ca1, Ato
if ( afpChain.getBlockNum() > 0){
// Superimpose ligands relative to the first block
- if( hetatms2.size() > 0 ) {
+ if(!hetatms2.isEmpty()) {
if ( afpChain.getBlockRotationMatrix().length > 0 ) {
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AtomCache.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AtomCache.java
index 1435191c2c..71b8a3da22 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AtomCache.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/util/AtomCache.java
@@ -228,7 +228,7 @@ public Structure getBiologicalAssembly(String pdbId, int bioAssemblyId, boolean
throws StructureException, IOException {
return getBiologicalAssembly(new PdbId(pdbId), bioAssemblyId, multiModel);
}
-
+
/**
* Returns the biological assembly for a given PDB ID and bioAssemblyId, by building the
* assembly from the biounit annotations found in {@link Structure#getPDBHeader()}
@@ -284,7 +284,7 @@ public Structure getBiologicalAssembly(PdbId pdbId, int bioAssemblyId, boolean m
asymUnit.getPDBHeader().getBioAssemblies().get(bioAssemblyId).getTransforms();
- if (transformations == null || transformations.size() == 0) {
+ if (transformations == null || transformations.isEmpty()) {
throw new StructureException("Could not load transformations to recreate biological assembly id " + bioAssemblyId + " of " + pdbId);
}
@@ -339,7 +339,7 @@ public Structure getBiologicalAssembly(String pdbId, boolean multiModel) throws
asymUnit.getPDBHeader().getBioAssemblies().get(bioAssemblyId).getTransforms();
- if (transformations == null || transformations.size() == 0) {
+ if (transformations == null || transformations.isEmpty()) {
throw new StructureException("Could not load transformations to recreate biological assembly id " + bioAssemblyId + " of " + pdbId);
}
@@ -385,7 +385,7 @@ public List getBiologicalAssemblies(String pdbId, boolean multiModel)
List transformations =
asymUnit.getPDBHeader().getBioAssemblies().get(bioAssemblyId).getTransforms();
- if (transformations == null || transformations.size() == 0) {
+ if (transformations == null || transformations.isEmpty()) {
logger.info("Could not load transformations to recreate biological assembly id {} of {}. Assembly " +
"id will be missing in biological assemblies.", bioAssemblyId, pdbId);
continue;
@@ -807,7 +807,7 @@ public Structure getStructureForPdbId(String id) throws IOException, StructureEx
public Structure getStructureForPdbId(PdbId pdbId) throws IOException {
if (pdbId == null)
return null;
-
+
while (checkLoading(pdbId)) {
// waiting for loading to be finished...
try {
@@ -833,7 +833,7 @@ public Structure getStructureForPdbId(PdbId pdbId) throws IOException {
protected Structure loadStructureFromCifByPdbId(String pdbId) throws IOException {
return loadStructureFromCifByPdbId(new PdbId(pdbId));
}
-
+
protected Structure loadStructureFromCifByPdbId(PdbId pdbId) throws IOException {
logger.debug("Loading structure {} from mmCIF file {}.", pdbId, path);
Structure s;
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/xml/MultipleAlignmentXMLParser.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/xml/MultipleAlignmentXMLParser.java
index 759ee61931..e8d5434578 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/align/xml/MultipleAlignmentXMLParser.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/align/xml/MultipleAlignmentXMLParser.java
@@ -169,7 +169,7 @@ else if ("ScoresCache".equals(child.getNodeName())){
}
}
//Because if it is 0 means that there were no transformations
- if (transforms.size() != 0){
+ if (!transforms.isEmpty()){
bs.setTransformations(transforms);
}
return bs;
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java
index 0f46b7f416..ed576b9abe 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java
@@ -27,7 +27,6 @@
import org.slf4j.LoggerFactory;
import javax.vecmath.Point3d;
-import javax.vecmath.Vector3d;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -101,12 +100,56 @@ public void run() {
}
}
- static class IndexAndDistance {
- final int index;
- final double dist;
- IndexAndDistance(int index, double dist) {
- this.index = index;
- this.dist = dist;
+ /**
+ * The neighbors of a single atom, as parallel primitive arrays of neighbor atom indices and their distances to
+ * the central atom, sorted by increasing distance.
+ *
+ * Parallel primitive arrays are used rather than an array of index-distance objects because there are ~30
+ * neighbors per atom: for a large structure that would mean millions of small short-lived objects.
+ */
+ static class Neighbors {
+
+ /** The neighbor atom indices, ordered by increasing distance to the central atom */
+ final int[] indices;
+ /** The distances to the central atom, in increasing order and parallel to {@link #indices} */
+ final double[] dists;
+
+ private Neighbors(int[] indices, double[] dists) {
+ this.indices = indices;
+ this.dists = dists;
+ }
+
+ /**
+ * Creates a Neighbors from the first count elements of the given buffers, copying them to exact-size arrays
+ * and sorting them by increasing distance.
+ * @param indicesBuffer the neighbor indices, only the first count elements are used
+ * @param distsBuffer the neighbor distances, only the first count elements are used
+ * @param count the number of neighbors
+ * @return the sorted neighbors
+ */
+ static Neighbors createSorted(int[] indicesBuffer, double[] distsBuffer, int count) {
+ int[] indices = Arrays.copyOf(indicesBuffer, count);
+ double[] dists = Arrays.copyOf(distsBuffer, count);
+ // Sorting by closest to farthest away neighbors achieves faster runtimes when checking for occluded
+ // sphere sample points in calcSingleAsa. This follows the ideas exposed in
+ // Eisenhaber et al, J Comp Chemistry 1994 (https://onlinelibrary.wiley.com/doi/epdf/10.1002/jcc.540160303)
+ // This is essential for performance: it brings down the number of occlusion checks to
+ // an average of n_sphere_points/10 per atom, producing ~ x4 performance gain overall.
+ // An insertion sort is used because the arrays are small (~30 elements on average) and because it avoids
+ // both the boxing of a comparator-based sort and the object allocation an index-distance array would need.
+ for (int i = 1; i < count; i++) {
+ double dist = dists[i];
+ int index = indices[i];
+ int j = i - 1;
+ while (j >= 0 && dists[j] > dist) {
+ dists[j + 1] = dists[j];
+ indices[j + 1] = indices[j];
+ j--;
+ }
+ dists[j + 1] = dist;
+ indices[j + 1] = index;
+ }
+ return new Neighbors(indices, dists);
}
}
@@ -116,9 +159,16 @@ static class IndexAndDistance {
private final double[] radii;
private final double probe;
private final int nThreads;
- private Vector3d[] spherePoints;
+ /**
+ * The sphere points to sample, as a flat array of interleaved x,y,z coordinates (thus of size 3 x nSpherePoints).
+ * A flat array of primitives (rather than an array of Vector3d objects) is used for performance: it keeps the
+ * points contiguous in memory and avoids a pointer dereference per point in the innermost loop of
+ * {@link #calcSingleAsa(int)}.
+ */
+ private double[] spherePoints;
+ private int nSpherePoints;
private double cons;
- private IndexAndDistance[][] neighborIndices;
+ private Neighbors[] neighbors;
private boolean useSpatialHashingForNeighbors;
@@ -239,7 +289,8 @@ private void initSpherePoints(int nSpherePoints) {
logger.debug("Will use {} sphere points", nSpherePoints);
// initialising the sphere points to sample
- spherePoints = generateSpherePoints(nSpherePoints);
+ this.nSpherePoints = nSpherePoints;
+ this.spherePoints = generateSpherePoints(nSpherePoints);
cons = 4.0 * Math.PI / nSpherePoints;
}
@@ -285,10 +336,10 @@ public double[] calculateAsas() {
long start = System.currentTimeMillis();
if (useSpatialHashingForNeighbors) {
logger.debug("Will use spatial hashing to find neighbors");
- neighborIndices = findNeighborIndicesSpatialHashing();
+ neighbors = findNeighborIndicesSpatialHashing();
} else {
logger.debug("Will not use spatial hashing to find neighbors");
- neighborIndices = findNeighborIndices();
+ neighbors = findNeighborIndices();
}
long end = System.currentTimeMillis();
logger.debug("Took {} s to find neighbors", (end-start)/1000.0);
@@ -334,109 +385,126 @@ void setUseSpatialHashingForNeighbors(boolean useSpatialHashingForNeighbors) {
* Returns list of 3d coordinates of points on a unit sphere using the
* Golden Section Spiral algorithm.
* @param nSpherePoints the number of points to be used in generating the spherical dot-density
- * @return the array of points as Vector3d objects
+ * @return a flat array of interleaved x,y,z coordinates, of size 3 x nSpherePoints
*/
- private Vector3d[] generateSpherePoints(int nSpherePoints) {
- Vector3d[] points = new Vector3d[nSpherePoints];
+ private double[] generateSpherePoints(int nSpherePoints) {
+ double[] points = new double[3 * nSpherePoints];
double inc = Math.PI * (3.0 - Math.sqrt(5.0));
double offset = 2.0 / nSpherePoints;
for (int k=0;k thisNbIndices = new ArrayList<>(initialCapacity);
+ int count = 0;
for (int i = 0; i < atomCoords.length; i++) {
if (i == k) continue;
double dist = atomCoords[i].distance(atomCoords[k]);
- if (dist < radius + radii[i]) {
- thisNbIndices.add(new IndexAndDistance(i, dist));
+ if (areNeighbors(k, i, dist)) {
+ if (count == indicesBuffer.length) {
+ indicesBuffer = Arrays.copyOf(indicesBuffer, count * 2);
+ distsBuffer = Arrays.copyOf(distsBuffer, count * 2);
+ }
+ indicesBuffer[count] = i;
+ distsBuffer[count] = dist;
+ count++;
}
}
- IndexAndDistance[] indicesArray = thisNbIndices.toArray(new IndexAndDistance[0]);
- nbsIndices[k] = indicesArray;
+ nbs[k] = Neighbors.createSorted(indicesBuffer, distsBuffer, count);
}
- return nbsIndices;
+ return nbs;
}
/**
- * Returns the 2-dimensional array with neighbor indices for every atom,
+ * Returns the neighbors of every atom, sorted by increasing distance,
* using spatial hashing to avoid all to all distance calculation.
- * @return 2-dimensional array of size: n_atoms x n_neighbors_per_atom
+ * @return array of size n_atoms
*/
- IndexAndDistance[][] findNeighborIndicesSpatialHashing() {
-
- // looking at a typical protein case, number of neighbours are from ~10 to ~50, with an average of ~30
- int initialCapacity = 60;
+ Neighbors[] findNeighborIndicesSpatialHashing() {
List contactList = calcContacts();
- Map> indices = new HashMap<>(atomCoords.length);
+
+ // A first pass to count the neighbors per atom, so that exact-size arrays can be allocated in the second
+ // pass. Atom indices are dense, so plain arrays are used rather than a map: that avoids boxing the indices
+ // and the repeated hashing, which are significant given that there are ~30 contacts per atom.
+ int[] counts = new int[atomCoords.length];
for (Contact contact : contactList) {
// note contacts are stored 1-way only, with j>i
int i = contact.getI();
int j = contact.getJ();
-
- List iIndices;
- List jIndices;
- if (!indices.containsKey(i)) {
- iIndices = new ArrayList<>(initialCapacity);
- indices.put(i, iIndices);
- } else {
- iIndices = indices.get(i);
- }
- if (!indices.containsKey(j)) {
- jIndices = new ArrayList<>(initialCapacity);
- indices.put(j, jIndices);
- } else {
- jIndices = indices.get(j);
- }
-
- double radius = radii[i] + probe + probe;
- double dist = contact.getDistance();
- if (dist < radius + radii[j]) {
- iIndices.add(new IndexAndDistance(j, dist));
- jIndices.add(new IndexAndDistance(i, dist));
+ if (areNeighbors(i, j, contact.getDistance())) {
+ counts[i]++;
+ counts[j]++;
}
}
- // convert map to array for fast access
- IndexAndDistance[][] nbsIndices = new IndexAndDistance[atomCoords.length][];
- for (Map.Entry> entry : indices.entrySet()) {
- List list = entry.getValue();
- IndexAndDistance[] indexAndDistances = list.toArray(new IndexAndDistance[0]);
- nbsIndices[entry.getKey()] = indexAndDistances;
+ int[][] indices = new int[atomCoords.length][];
+ double[][] dists = new double[atomCoords.length][];
+ for (int i = 0; i < atomCoords.length; i++) {
+ // note that some atoms might have no neighbors at all, in which case these are empty arrays
+ indices[i] = new int[counts[i]];
+ dists[i] = new double[counts[i]];
}
- // important: some atoms might have no neighbors at all: we need to initialise to empty arrays
- for (int i=0; i calcContacts() {
private double calcSingleAsa(int i) {
Point3d atom_i = atomCoords[i];
- int n_neighbor = neighborIndices[i].length;
- IndexAndDistance[] neighbor_indices = neighborIndices[i];
- // Sorting by closest to farthest away neighbors achieves faster runtimes when checking for occluded
- // sphere sample points below. This follows the ideas exposed in
- // Eisenhaber et al, J Comp Chemistry 1994 (https://onlinelibrary.wiley.com/doi/epdf/10.1002/jcc.540160303)
- // This is essential for performance. In my tests this brings down the number of occlusion checks in loop below to
- // an average of n_sphere_points/10 per atom i, producing ~ x4 performance gain overall
- Arrays.sort(neighbor_indices, Comparator.comparingDouble(o -> o.dist));
+ // note the neighbors are already sorted by increasing distance (see Neighbors#createSorted), which is
+ // essential for the performance of the occlusion checks in the loop below
+ Neighbors nbs = neighbors[i];
+ int[] neighbor_indices = nbs.indices;
+ double[] neighbor_dists = nbs.dists;
+ int n_neighbor = neighbor_indices.length;
double radius_i = probe + radii[i];
@@ -476,36 +542,47 @@ private double calcSingleAsa(int i) {
int[] numDistsCalced = null;
if (logger.isDebugEnabled()) numDistsCalced = new int[n_neighbor];
- // now we precalculate anything depending only on i,j in equation 3 in Eisenhaber 1994
- double[] sqRadii = new double[n_neighbor];
- Vector3d[] aj_minus_ais = new Vector3d[n_neighbor];
+ // Now we precalculate anything depending only on i,j in equation 3 in Eisenhaber 1994.
+ // The per-neighbor data is laid out in a single flat array as quadruplets
+ // [aj_minus_ai.x, aj_minus_ai.y, aj_minus_ai.z, cutoff], so that the innermost loop below is a purely
+ // sequential scan over contiguous primitives, with no object dereferencing. That matches the access
+ // pattern of the early break and is significantly faster than an array of Vector3d objects.
+ double[] nbData = new double[4 * n_neighbor];
for (int nbArrayInd =0; nbArrayInd> 2]++;
- if (dotProd > sqRadii[nbArrayInd]) {
+ if (dotProd > nbData[off + 3]) {
is_accessible = false;
break;
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java
index 3f0f44158b..4201f8d5ca 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/cath/CathInstallation.java
@@ -31,8 +31,6 @@
import java.io.*;
import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.StandardCopyOption;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
@@ -54,7 +52,7 @@ public class CathInstallation implements CathDatabase{
public static final String nodeListFileName = "cath-names-v%s.txt";
public static final String domallFileName = "cath-domain-boundaries-v%s.txt";
- public static final String CATH_DOWNLOAD_URL = "http://download.cathdb.info/cath/releases/";
+ public static final String CATH_DOWNLOAD_URL = "https://download.cathdb.info/cath/releases/";
public static final String CATH_DOWNLOAD_ALL_RELEASES_DIR = "all-releases";
public static final String CATH_DOWNLOAD_CLASSIFICATION_DATA_DIR = "cath-classification-data";
@@ -352,13 +350,15 @@ private void parseCathDomainList() throws IOException {
parseCathDomainList(buffer);
}
- private void parseCathDomainList(BufferedReader bufferedReader) throws IOException{
+ protected void parseCathDomainList(BufferedReader bufferedReader) throws IOException{
String line;
- // int counter = 0;
+ int counter = 0;
while ( (line = bufferedReader.readLine()) != null ) {
if ( line.startsWith("#") ) continue;
+ if ( line.trim().isEmpty() ) continue;
CathDomain cathDomain = parseCathListFileLine(line);
- // counter++;
+ if ( cathDomain == null ) continue;
+ counter++;
String pdbId = cathDomain.getPdbIdAndChain().substring(0,4); // includes chain letter
@@ -374,6 +374,9 @@ private void parseCathDomainList(BufferedReader bufferedReader) throws IOExcepti
domainMap.put( cathDomain.getDomainName(), cathDomain );
}
+ if (counter == 0) {
+ throw new IOException("Could not parse any CATH domains from the domain list file.");
+ }
}
private void parseCathNames() throws IOException {
@@ -388,7 +391,9 @@ private void parseCathNames(BufferedReader bufferedReader) throws IOException{
//int counter = 0;
while ( (line = bufferedReader.readLine()) != null ) {
if ( line.startsWith("#") ) continue;
+ if ( line.trim().isEmpty() ) continue;
CathNode cathNode = parseCathNamesFileLine(line);
+ if ( cathNode == null ) continue;
cathTree.put(cathNode.getNodeId(), cathNode);
}
}
@@ -415,6 +420,7 @@ private void parseCathDomainDescriptionFile(BufferedReader bufferedReader) throw
StringBuilder sseqs = null;
while ( (line = bufferedReader.readLine()) != null ) {
if ( line.startsWith("#") ) continue;
+ if ( line.trim().isEmpty() ) continue;
if ( line.startsWith("FORMAT") ) {
cathDescription = new CathDomain();
cathDescription.setFormat( line.substring(10) );
@@ -506,8 +512,11 @@ private void parseCathDomainDescriptionFile(BufferedReader bufferedReader) throw
}*/
private CathDomain parseCathListFileLine(String line) {
+ String [] token = line.trim().split("\\s+");
+ if (token.length < 12) {
+ return null;
+ }
CathDomain cathDomain = new CathDomain();
- String [] token = line.split("\\s+");
cathDomain.setDomainName(token[0]);
cathDomain.setClassId(Integer.parseInt(token[1]));
cathDomain.setArchitectureId(Integer.parseInt(token[2]));
@@ -524,8 +533,12 @@ private CathDomain parseCathListFileLine(String line) {
}
private CathNode parseCathNamesFileLine(String line) {
+ String[] token = line.trim().split("\\s+",3);
+ if (token.length < 3) {
+ LOGGER.debug("Invalid line in cath names file, was expecting 3 tokens but got {} tokens: {}", token.length, line);
+ return null;
+ }
CathNode cathNode = new CathNode();
- String[] token = line.split("\\s+",3);
cathNode.setNodeId( token[0] );
int idx = token[0].lastIndexOf(".");
if ( idx == -1 ) idx = token[0].length();
@@ -546,8 +559,8 @@ private void parseCathDomall(BufferedReader bufferedReader) throws IOException{
String line;
while ( ((line = bufferedReader.readLine()) != null) ) {
if ( line.startsWith("#") ) continue;
- if ( line.length() == 0 ) continue;
- String[] token = line.split("\\s+");
+ if ( line.trim().isEmpty() ) continue;
+ String[] token = line.trim().split("\\s+");
String chainId = token[0];
Integer numberOfDomains = Integer.parseInt( token[1].substring(1) );
Integer numberOfFragments = Integer.parseInt( token[2].substring(1) );
@@ -637,27 +650,26 @@ private void parseCathDomall(BufferedReader bufferedReader) throws IOException{
}
protected void downloadFileFromRemote(URL remoteURL, File localFile) throws IOException{
-// System.out.println("downloading " + remoteURL + " to: " + localFile);
LOGGER.info("Downloading file {} to local file {}", remoteURL, localFile);
long timeS = System.currentTimeMillis();
- File tempFile = Files.createTempFile(FileDownloadUtils.getFilePrefix(localFile),"." + FileDownloadUtils.getFileExtension(localFile)).toFile();
- FileOutputStream out = new FileOutputStream(tempFile);
-
- InputStream in = remoteURL.openStream();
- byte[] buf = new byte[4 * 1024]; // 4K buffer
- int bytesRead;
- while ((bytesRead = in.read(buf)) != -1) {
- out.write(buf, 0, bytesRead);
+ File parent = localFile.getAbsoluteFile().getParentFile();
+ if (parent != null && !parent.isDirectory() && !parent.mkdirs()) {
+ throw new IOException("Could not create directory " + parent);
}
- in.close();
- out.close();
- Files.copy(tempFile.toPath(), localFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ // Previously this read the response with a bare remoteURL.openStream() and
+ // copied whatever came back. That silently accepted error and redirect
+ // responses: when download.cathdb.info began redirecting http to https, the
+ // body of the 301 was written here as though it were classification data,
+ // and the failure only surfaced much later as an unparseable file.
+ FileDownloadUtils.downloadFileWithValidation(remoteURL, localFile, null,
+ FileDownloadUtils.Hash.UNKNOWN, FileDownloadUtils.ETagPolicy.USE_IF_HEX_DIGEST);
- // delete the tmp file
- tempFile.delete();
+ if (!FileDownloadUtils.validateFile(localFile)) {
+ throw new IOException("Downloaded file invalid: " + localFile);
+ }
long size = localFile.length();
@@ -674,25 +686,25 @@ protected void downloadFileFromRemote(URL remoteURL, File localFile) throws IOEx
private boolean domainDescriptionFileAvailable(){
String fileName = getDomainDescriptionFileName();
File f = new File(fileName);
- return f.exists();
+ return f.exists() && FileDownloadUtils.validateFile(f);
}
private boolean domainListFileAvailable(){
String fileName = getDomainListFileName();
File f = new File(fileName);
- return f.exists();
+ return f.exists() && FileDownloadUtils.validateFile(f);
}
private boolean nodeListFileAvailable(){
String fileName = getNodeListFileName();
File f = new File(fileName);
- return f.exists();
+ return f.exists() && FileDownloadUtils.validateFile(f);
}
private boolean domallFileAvailable() {
String fileName = getDomallFileName();
File f= new File(fileName);
- return f.exists();
+ return f.exists() && FileDownloadUtils.validateFile(f);
}
protected void downloadDomainListFile() throws IOException{
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/DownloadChemCompProvider.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/DownloadChemCompProvider.java
index 9eb9c7c6cf..e19535bce8 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/DownloadChemCompProvider.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/DownloadChemCompProvider.java
@@ -1,5 +1,6 @@
package org.biojava.nbio.structure.chem;
+import org.biojava.nbio.core.util.FileDownloadUtils;
import org.biojava.nbio.core.util.InputStreamProvider;
import org.biojava.nbio.structure.align.util.URLConnectionTools;
import org.biojava.nbio.structure.align.util.UserConfiguration;
@@ -396,6 +397,17 @@ private static boolean downloadChemCompRecord(String recordName) {
url = new URL(u);
URLConnection uconn = URLConnectionTools.openURLConnection(url);
+ // A 4xx or 5xx already fails safely, because getInputStream() throws for
+ // those. A redirect does not: if the server answers 3xx and the JDK
+ // declines to follow it - which it always does when the redirect changes
+ // http to https - getInputStream() hands back the body of the redirect
+ // instead. That body is short but not empty, so the "did we read any
+ // lines" check below accepts it, and it is gzipped and stored under the
+ // component's name. Every later lookup then reads it back and fails to
+ // parse, long after the request that caused it. This is exactly how the
+ // CATH downloader broke when download.cathdb.info moved to https.
+ FileDownloadUtils.checkHttpStatus(uconn);
+
try (PrintWriter pw = new PrintWriter(new GZIPOutputStream(new FileOutputStream(newFile)));
BufferedReader fileBuffer = new BufferedReader(new InputStreamReader(uconn.getInputStream()))) {
String line;
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/ZipChemCompProvider.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/ZipChemCompProvider.java
index 4fe19aca58..a68019efb7 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/ZipChemCompProvider.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/chem/ZipChemCompProvider.java
@@ -120,7 +120,7 @@ public ChemComp getChemComp(String recordName) {
}
// If a null record or an empty chemcomp, return a default ChemComp and blacklist.
- if (cc == null || (null == cc.getName() && cc.getAtoms().size() == 0)) {
+ if (cc == null || (null == cc.getName() && cc.getAtoms().isEmpty())) {
s_logger.info("Unable to find or download {} - excluding from future searches.", recordName);
unavailable.add(recordName);
return getEmptyChemComp(recordName);
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitCluster.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitCluster.java
index 9a87e92f88..87c3c06e3c 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitCluster.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitCluster.java
@@ -331,7 +331,7 @@ public boolean mergeIdenticalByEntityId(SubunitCluster other) {
}
}
- if (thisAligned.size() == 0 && otherAligned.size() == 0) {
+ if (thisAligned.isEmpty() && otherAligned.isEmpty()) {
logger.warn("No equivalent aligned atoms found between SubunitClusters {}-{} via entity SEQRES alignment. Is FileParsingParameters.setAlignSeqRes() set?", thisName, otherName);
}
@@ -507,27 +507,24 @@ public boolean mergeStructure(SubunitCluster other, SubunitClustererParameters p
}
}
- AFPChain afp = aligner.align(this.subunits.get(this.representative)
- .getRepresentativeAtoms(),
- other.subunits.get(other.representative)
- .getRepresentativeAtoms());
+ AFPChain afp = aligner.align(this.subunits.get(this.representative).getRepresentativeAtoms(),
+ other.subunits.get(other.representative).getRepresentativeAtoms());
+ String pairName = this.subunits.get(this.representative).getName() + "-" + other.subunits.get(other.representative).getName();
if (afp.getOptLength() < 1) {
// alignment failed (eg if chains were too short)
throw new StructureException(
- String.format("Subunits failed to align using %s", params.getSuperpositionAlgorithm()));
+ String.format("Subunits %s failed to align using %s", pairName, params.getSuperpositionAlgorithm()));
}
// Convert AFPChain to MultipleAlignment for convenience
MultipleAlignment msa = new MultipleAlignmentEnsembleImpl(
afp,
this.subunits.get(this.representative).getRepresentativeAtoms(),
- other.subunits.get(other.representative)
- .getRepresentativeAtoms(), false)
- .getMultipleAlignment(0);
+ other.subunits.get(other.representative).getRepresentativeAtoms(),
+ false).getMultipleAlignment(0);
- double structureCoverage = Math.min(msa.getCoverages().get(0), msa
- .getCoverages().get(1));
+ double structureCoverage = Math.min(msa.getCoverages().get(0), msa.getCoverages().get(1));
if(params.isUseStructureCoverage() && structureCoverage < params.getStructureCoverageThreshold()) {
return false;
@@ -543,8 +540,7 @@ public boolean mergeStructure(SubunitCluster other, SubunitClustererParameters p
return false;
}
- logger.info(String.format("SubunitClusters are structurally similar with "
- + "%.2f RMSD %.2f coverage", rmsd, structureCoverage));
+ logger.info("SubunitClusters {} are structurally similar with [ {} ] RMSD and [ {} ] coverage", pairName, String.format("%.2f", rmsd), String.format("%.2f", structureCoverage));
// Merge clusters
List> alignedRes = msa.getBlock(0).getAlignRes();
@@ -565,13 +561,18 @@ public boolean mergeStructure(SubunitCluster other, SubunitClustererParameters p
// Only consider residues that are part of the SubunitCluster
if (this.subunitEQR.get(this.representative).contains(thisIndex)
- && other.subunitEQR.get(other.representative).contains(
- otherIndex)) {
+ && other.subunitEQR.get(other.representative).contains(otherIndex)) {
thisAligned.add(thisIndex);
otherAligned.add(otherIndex);
}
}
+ // this can happen in very rare cases, e.g. 9y9z when merging E_1 into the cluster D_1, OM_1, Y_1
+ if (thisAligned.isEmpty() && otherAligned.isEmpty()) {
+ logger.warn("No equivalent aligned atoms found between SubunitClusters {} via structure alignment. Will not merge the second one into the first.", pairName);
+ return false;
+ }
+
updateEquivResidues(other, thisAligned, otherAligned);
this.method = SubunitClustererMethod.STRUCTURE;
@@ -602,18 +603,12 @@ private void updateEquivResidues(SubunitCluster other, List thisAligned
Collections.sort(otherRemove);
Collections.reverse(otherRemove);
- for (int t = 0; t < thisRemove.size(); t++) {
- for (List eqr : this.subunitEQR) {
- int column = thisRemove.get(t);
- eqr.remove(column);
- }
+ for (int column : thisRemove) {
+ this.subunitEQR.forEach(eqr -> eqr.remove(column));
}
- for (int t = 0; t < otherRemove.size(); t++) {
- for (List eqr : other.subunitEQR) {
- int column = otherRemove.get(t);
- eqr.remove(column);
- }
+ for (int column : otherRemove) {
+ other.subunitEQR.forEach(eqr -> eqr.remove(column));
}
// The representative is the longest sequence
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitClusterer.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitClusterer.java
index 6295f8fdf0..fa63b96d96 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitClusterer.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/cluster/SubunitClusterer.java
@@ -58,7 +58,7 @@ public static Stoichiometry cluster(Structure structure,
public static Stoichiometry cluster(List subunits, SubunitClustererParameters params) {
List clusters = new ArrayList<>();
- if (subunits.size() == 0)
+ if (subunits.isEmpty())
return new Stoichiometry(clusters);
// First generate a new cluster for each Subunit
@@ -83,8 +83,7 @@ public static Stoichiometry cluster(List subunits, SubunitClustererPara
}
} catch (CompoundNotFoundException e) {
- logger.warn("Could not merge by Sequence. {}",
- e.getMessage());
+ logger.info("Could not merge by Sequence. {}", e.getMessage());
}
}
}
@@ -100,7 +99,7 @@ public static Stoichiometry cluster(List subunits, SubunitClustererPara
clusters.remove(c2);
}
} catch (StructureException e) {
- logger.warn("Could not merge by Structure. {}", e.getMessage());
+ logger.info("Could not merge by Structure. {}", e.getMessage());
}
}
}
@@ -112,8 +111,7 @@ public static Stoichiometry cluster(List subunits, SubunitClustererPara
try {
clusters.get(c).divideInternally(params);
} catch (StructureException e) {
- logger.warn("Error analyzing internal symmetry. {}",
- e.getMessage());
+ logger.info("Error analyzing internal symmetry. {}", e.getMessage());
}
}
@@ -125,8 +123,7 @@ public static Stoichiometry cluster(List subunits, SubunitClustererPara
if (clusters.get(c1).mergeStructure(clusters.get(c2), params))
clusters.remove(c2);
} catch (StructureException e) {
- logger.warn("Could not merge by Structure. {}",
- e.getMessage());
+ logger.info("Could not merge by Structure. {}", e.getMessage());
}
}
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/GroupContact.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/GroupContact.java
index 07b163730d..245d4481f6 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/GroupContact.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/GroupContact.java
@@ -57,7 +57,7 @@ public void setPair(Pair pair) {
}
public double getMinDistance() {
- if (atomContacts.size()==0) return 0;
+ if (atomContacts.isEmpty()) return 0;
double minDistance = Double.MAX_VALUE;
for (AtomContact atomContact:atomContacts) {
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java
index 60f7c3a91b..00cf7ef65c 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java
@@ -380,7 +380,7 @@ public List getClusters(double contactOverlapScoreClu
clusters = new ArrayList<>();
// nothing to do if we have no interfaces
- if (list.size()==0) return clusters;
+ if (list.isEmpty()) return clusters;
logger.debug("Calculating all-vs-all Jaccard scores for {} interfaces", list.size());
double[][] matrix = new double[list.size()][list.size()];
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/ecod/EcodInstallation.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/ecod/EcodInstallation.java
index f4be5cd4f5..027907ffa6 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/ecod/EcodInstallation.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/ecod/EcodInstallation.java
@@ -137,9 +137,15 @@ public List getDomainsForPdb(String id) throws IOException {
// unlock to allow ensureDomainsFileInstalled to get the write lock
logger.trace("UNLOCK readlock");
domainsFileLock.readLock().unlock();
- indexDomains();
- domainsFileLock.readLock().lock();
- logger.trace("LOCK readlock");
+ try {
+ indexDomains();
+ } finally {
+ // re-acquire even if indexing failed, so the outer finally has a
+ // lock to release; otherwise IllegalMonitorStateException replaces
+ // the real cause and the failure becomes unreadable
+ domainsFileLock.readLock().lock();
+ logger.trace("LOCK readlock");
+ }
}
PdbId pdbId = null;
@@ -244,9 +250,15 @@ public List getAllDomains() throws IOException {
// unlock to allow ensureDomainsFileInstalled to get the write lock
logger.trace("UNLOCK readlock");
domainsFileLock.readLock().unlock();
- ensureDomainsFileInstalled();
- domainsFileLock.readLock().lock();
- logger.trace("LOCK readlock");
+ try {
+ ensureDomainsFileInstalled();
+ } finally {
+ // re-acquire even if the download failed, so the outer finally has a
+ // lock to release; otherwise IllegalMonitorStateException replaces
+ // the real cause and the failure becomes unreadable
+ domainsFileLock.readLock().lock();
+ logger.trace("LOCK readlock");
+ }
}
return allDomains;
} finally {
@@ -272,12 +284,41 @@ public void clear() {
*
* Note that this may differ from the version requested in the constructor
* for the special case of "latest"
+ *
+ * Since 7.3.0 this reads only the file's header rather than parsing the whole
+ * file, so it no longer has the side effect of loading every domain.
* @return the ECOD version
* @throws IOException If an error occurs while downloading or parsing the file
*/
@Override
public String getVersion() throws IOException {
- ensureDomainsFileInstalled();
+ domainsFileLock.readLock().lock();
+ logger.trace("LOCK readlock");
+ try {
+ if( parsedVersion != null ) {
+ return parsedVersion;
+ }
+ } finally {
+ logger.trace("UNLOCK readlock");
+ domainsFileLock.readLock().unlock();
+ }
+
+ // The version is declared in the first few lines of the file, so read those rather
+ // than the millions of domain records behind them. The current release is 657 MB and
+ // holds nearly three million records; parsing it in full to answer this question
+ // costs over a gigabyte of heap and several seconds.
+ ensureDomainsFileDownloaded();
+
+ domainsFileLock.writeLock().lock();
+ logger.trace("LOCK writelock");
+ try {
+ if( parsedVersion == null ) {
+ parsedVersion = parseVersionOnly();
+ }
+ } finally {
+ logger.trace("UNLOCK writelock");
+ domainsFileLock.writeLock().unlock();
+ }
if( parsedVersion == null) {
return requestedVersion;
@@ -285,6 +326,30 @@ public String getVersion() throws IOException {
return parsedVersion;
}
+ /**
+ * Reads the version from the header of the local domains file without parsing the
+ * domains themselves.
+ * @return the version, or null if the header does not declare one
+ * @throws IOException if the file cannot be read
+ * @since 7.3.0
+ */
+ private String parseVersionOnly() throws IOException {
+ try( BufferedReader in = new BufferedReader(new FileReader(getDomainFile())) ) {
+ String line;
+ while( (line = in.readLine()) != null ) {
+ Matcher match = EcodParser.VERSION_RE.matcher(line);
+ if( match.matches() ) {
+ return match.group(1);
+ }
+ if( !line.startsWith("#") ) {
+ // past the header block; from v294.1 the column names are not commented
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Get the top-level ECOD server URL. Defaults to "http://prodata.swmed.edu"
* @return the url to the ecod server
@@ -325,6 +390,24 @@ public void setCacheLocation(String cacheLocation) {
domainsFileLock.writeLock().unlock();
}
+ /**
+ * Ensures the domains file is present and current locally, without parsing it.
+ * @throws IOException in cases of file I/O, including failure to download a healthy file
+ * @since 7.3.0
+ */
+ private void ensureDomainsFileDownloaded() throws IOException {
+ domainsFileLock.writeLock().lock();
+ logger.trace("LOCK writelock");
+ try {
+ if( !domainsAvailable() ) {
+ downloadDomains();
+ }
+ } finally {
+ logger.trace("UNLOCK writelock");
+ domainsFileLock.writeLock().unlock();
+ }
+ }
+
/**
* Blocks until ECOD domains file has been downloaded and parsed.
*
@@ -549,6 +632,24 @@ Current version (1.4) contains the following columns:
v1.2 - added f-group identifiers to fasta file, domain description file. ECODf identifiers now used when available for F-group name.
Domain assemblies now represented by assembly uid in domain assembly status.
v1.4 - added seqid_range and headers (develop101)
+v1.6 - renamed column 4 from f_id to t_id and inserted unp_acc (UniProt accession) as
+ column 9, giving 16 columns (seen in develop291)
+
+From v294.1 the distribution was redesigned. The header comment changed from
+"#ECOD version develop291" to "# Version: v294.1", the column header row is no longer
+commented out, and the columns became:
+
+ uid ecod_domain_id manual_rep f_id pdb chain pdb_range seqid_range architecture_name
+ x_name h_name t_name f_name assembly_id domain_id_short range_count arch_manual
+ x_manual h_manual t_manual f_manual valid_structure ligand_binding
+
+v295 appends ligand_comp_ids and ligand_pdbnum, for 25 columns. Also note that
+manual_rep now holds True/False rather than MANUAL_REP/AUTO_NONREP, that assembly_id
+and domain_id_short are empty on every row, that f_name is empty rather than
+F_UNCLASSIFIED for unclassified domains, and that uid restarts from 0.
+
+Because the columns have been renamed, reordered and added to repeatedly, files that
+declare a column header are read by column name rather than by position.
*/
/** String for unclassified F-groups */
@@ -561,10 +662,28 @@ Current version (1.4) contains the following columns:
public static final String IS_REPRESENTATIVE = "MANUAL_REP";
/** Indicates not a manual representative */
public static final String NOT_REPRESENTATIVE = "AUTO_NONREP";
+ /**
+ * Matches the comment declaring the version, which has taken two forms:
+ * {@code #ECOD version develop291} up to develop292, and {@code # Version: v295}
+ * from v294.1 onwards.
+ * @since 7.3.0
+ */
+ static final Pattern VERSION_RE = Pattern.compile(
+ "^\\s*#\\s*(?:ECOD\\s+)?version\\s*:?\\s*(\\S+).*", Pattern.CASE_INSENSITIVE);
private List domains;
private String version;
+ // prevent too many warnings; negative numbers print all warnings
+ private int warnIsDomainAssembly = 1;
+ private int warnHierarchicalFormat = 5;
+ private int warnNumberOfFields = 10;
+ private int warnNumberFormat = 10;
+ /** Data lines that could not be turned into a domain, for the summary at the end */
+ private int skippedLines = 0;
+ /** Data lines describing a domain in a computed model rather than a PDB entry */
+ private int modelLines = 0;
+
public EcodParser(String filename) throws IOException {
this(new File(filename));
}
@@ -584,30 +703,55 @@ private void parse(BufferedReader in) throws IOException {
// Allocate plenty of space for ECOD as of 2015
ArrayList domainsList = new ArrayList<>(500000);
- Pattern versionRE = Pattern.compile("^\\s*#.*ECOD\\s*version\\s+(\\S+).*");
Pattern commentRE = Pattern.compile("^\\s*#.*");
- // prevent too many warnings; negative numbers print all warnings
- int warnIsDomainAssembly = 1;
- int warnHierarchicalFormat = 5;
- int warnNumberOfFields = 10;
+ ColumnLayout layout = null;
String line = in.readLine();
int lineNum = 1;
while( line != null ) {
// Check for requestedVersion string
- Matcher match = versionRE.matcher(line);
+ Matcher match = VERSION_RE.matcher(line);
if(match.matches()) {
// special requestedVersion comment
this.version = match.group(1);
+ } else if( ColumnLayout.isColumnHeader(line) ) {
+ // The column names. Since the columns have been renamed, reordered and
+ // added to several times, later lines are read by name rather than by
+ // position wherever this header is present (develop101 onwards).
+ layout = ColumnLayout.fromHeader(line);
+ logger.debug("Read ECOD column header at line {}: {} columns",lineNum,layout.size());
} else {
match = commentRE.matcher(line);
if(match.matches()) {
// ignore comments
} else {
- // data line
- String[] fields = line.split("\t");
- if( fields.length == 13 || fields.length == 14 || fields.length == 15) {
+ // data line. The last column is frequently empty, so keep trailing
+ // empty fields rather than letting split() discard them.
+ String[] fields = line.split("\t", -1);
+ if( layout != null ) {
+ String pdb = layout.get(fields, "pdb");
+ if( pdb != null && pdb.isEmpty() ) {
+ // From v294.1 the distribution also classifies domains
+ // found in computed (AlphaFold) models, which have no PDB
+ // entry and so cannot be represented by an EcodDomain.
+ modelLines++;
+ } else {
+ try {
+ EcodDomain domain = parseDomain(fields, layout, lineNum);
+ if(domain != null) {
+ domainsList.add(domain);
+ } else {
+ skippedLines++;
+ warnMissingColumns(lineNum);
+ }
+ } catch(IllegalArgumentException e) {
+ // includes NumberFormatException and an unusable PDB id
+ skippedLines++;
+ warnUnparseableLine(lineNum, e);
+ }
+ }
+ } else if( fields.length == 13 || fields.length == 14 || fields.length == 15) {
try {
int i = 0; // field number, to allow future insertion of fields
@@ -620,32 +764,16 @@ private void parse(BufferedReader in) throws IOException {
// Manual column may be missing in version 1.0 files
Boolean manual = null;
if( fields.length >= 14) {
- String manualString = fields[i++];
- if(manualString.equalsIgnoreCase(IS_REPRESENTATIVE)) {
- manual = true;
- } else if(manualString.equalsIgnoreCase(NOT_REPRESENTATIVE)) {
- manual = false;
- } else {
- logger.warn("Unexpected value for manual field: {} in line {}",manualString,lineNum);
- }
+ manual = parseManualRep(fields[i++], lineNum);
}
//Column 4: ECOD hierachy identifier - [X-group].[H-group].[T-group].[F-group]
// hierarchical field, e.g. "1.1.4.1"
- String[] xhtGroup = fields[i++].split("\\.");
- if(xhtGroup.length < 3 || 4 < xhtGroup.length) {
- if(warnHierarchicalFormat > 1) {
- logger.warn("Unexpected format for hierarchical field \"{}\" in line {}",fields[i-1],lineNum);
- warnHierarchicalFormat--;
- } else if(warnHierarchicalFormat != 0) {
- logger.warn("Unexpected format for hierarchical field \"{}\" in line {}. Not printing future similar warnings.",fields[i-1],lineNum);
- warnHierarchicalFormat--;
- }
- }
- Integer xGroup = xhtGroup.length>0 ? Integer.parseInt(xhtGroup[0]) : null;
- Integer hGroup = xhtGroup.length>1 ? Integer.parseInt(xhtGroup[1]) : null;
- Integer tGroup = xhtGroup.length>2 ? Integer.parseInt(xhtGroup[2]) : null;
- Integer fGroup = xhtGroup.length>3 ? Integer.parseInt(xhtGroup[3]) : null;
+ Integer[] xhtfGroup = parseHierarchy(fields[i++], lineNum);
+ Integer xGroup = xhtfGroup[0];
+ Integer hGroup = xhtfGroup[1];
+ Integer tGroup = xhtfGroup[2];
+ Integer fGroup = xhtfGroup[3];
//Column 5: PDB identifier
String pdbId = fields[i++];
@@ -699,32 +827,18 @@ private void parse(BufferedReader in) throws IOException {
assemblyId = Long.parseLong(assemblyStr);
}
- String ligandStr = fields[i++];
- Set ligands = null;
- if( "NO_LIGANDS_4A".equals(ligandStr) || ligandStr.isEmpty() ) {
- ligands = Collections.emptySet();
- } else {
- String[] ligSplit = ligandStr.split(",");
- ligands = new LinkedHashSet<>(ligSplit.length);
- for(String s : ligSplit) {
- ligands.add(s.intern());
- }
- }
+ Set ligands = parseLigands(fields[i++]);
EcodDomain domain = new EcodDomain(uid, domainId, manual, xGroup, hGroup, tGroup, fGroup,pdbId, chainId, range, seqId, architectureName, xGroupName, hGroupName, tGroupName, fGroupName, assemblyId, ligands);
domainsList.add(domain);
} catch(NumberFormatException e) {
- logger.warn("Error in ECOD parsing at line "+lineNum,e);
+ skippedLines++;
+ warnUnparseableLine(lineNum, e);
}
} else {
- if(warnNumberOfFields > 1) {
- logger.warn("Unexpected number of fields in line {}.",lineNum);
- warnNumberOfFields--;
- } else if(warnNumberOfFields == 0) {
- logger.warn("Unexpected number of fields in line {}. Not printing future similar warnings",lineNum);
- warnIsDomainAssembly--;
- }
+ skippedLines++;
+ warnMissingColumns(lineNum);
}
}
}
@@ -737,6 +851,23 @@ private void parse(BufferedReader in) throws IOException {
else
logger.info("Parsed {} ECOD domains from version {}",domainsList.size(),this.version);
+ if(modelLines > 0) {
+ logger.info("Ignored {} ECOD domains classified from computed models, "
+ + "which have no PDB entry", modelLines);
+ }
+
+ if(domainsList.isEmpty() && skippedLines > 0) {
+ // Returning an empty list quietly is how an upstream format change went
+ // unnoticed for eight months. Say so instead.
+ logger.error("Parsed no ECOD domains from {} data lines of version {}. "
+ + "The file format has probably changed; please report this at "
+ + "https://github.com/biojava/biojava/issues", skippedLines,
+ this.version == null ? "unknown" : this.version);
+ } else if(skippedLines > 0) {
+ logger.warn("Skipped {} of {} ECOD data lines that could not be parsed",
+ skippedLines, skippedLines + domainsList.size());
+ }
+
this.domains = Collections.unmodifiableList( domainsList );
@@ -747,6 +878,169 @@ private void parse(BufferedReader in) throws IOException {
}
}
+ /**
+ * Builds a domain from a data line using the column names the file declares in its
+ * header, rather than fixed offsets. This is what allows one parser to read the
+ * 15-column develop101 layout, the 16-column develop291 layout (which inserts
+ * {@code unp_acc}) and the 23- and 25-column v294.1 and v295 layouts.
+ * @param fields the tab-separated values of one data line
+ * @param layout the column names read from the file's header
+ * @param lineNum the line number, for warnings
+ * @return the domain, or null if the line does not carry every required column
+ * @throws NumberFormatException if a numeric column does not hold a number
+ * @since 7.3.0
+ */
+ private EcodDomain parseDomain(String[] fields, ColumnLayout layout, int lineNum) {
+ String uidStr = layout.get(fields, "uid");
+ String domainId = layout.get(fields, "ecod_domain_id");
+ // renamed from t_id to f_id when the hierarchy gained a fourth level
+ String hierarchy = layout.get(fields, "f_id", "t_id");
+ String pdbId = layout.get(fields, "pdb");
+ String chainId = layout.get(fields, "chain");
+ String range = layout.get(fields, "pdb_range");
+ if( uidStr == null || domainId == null || hierarchy == null
+ || pdbId == null || chainId == null || range == null ) {
+ return null;
+ }
+
+ Long uid = Long.parseLong(uidStr);
+ Boolean manual = parseManualRep(layout.get(fields, "manual_rep"), lineNum);
+ Integer[] xhtfGroup = parseHierarchy(hierarchy, lineNum);
+ // absent before version 1.4
+ String seqId = layout.get(fields, "seqid_range");
+
+ String architectureName = internName(layout.get(fields, "architecture_name", "arch_name"));
+ String xGroupName = internName(layout.get(fields, "x_name"));
+ String hGroupName = internName(layout.get(fields, "h_name"));
+ String tGroupName = internName(layout.get(fields, "t_name"));
+ // Up to develop292 an unclassified domain carried F_UNCLASSIFIED here. From
+ // v294.1 the name is simply empty, while f_id still classifies the domain to
+ // four levels, so the two are no longer equivalent and the empty value is
+ // deliberately left as it is rather than translated.
+ String fGroupName = internName(layout.get(fields, "f_name"));
+
+ // v294.1 and later declare assembly_id but leave it empty on every row, which
+ // means the same as the NOT_DOMAIN_ASSEMBLY of earlier versions.
+ Long assemblyId = null;
+ String assemblyStr = layout.get(fields, "assembly_id", "asm_status");
+ if( assemblyStr == null || assemblyStr.isEmpty() || NOT_DOMAIN_ASSEMBLY.equals(assemblyStr) ) {
+ assemblyId = uid;
+ } else if( IS_DOMAIN_ASSEMBLY.equals(assemblyStr) ) {
+ warnDomainAssembly(lineNum);
+ } else {
+ assemblyId = Long.parseLong(assemblyStr);
+ }
+
+ // the ligand list moved from the last column to ligand_comp_ids in v295
+ Set ligands = parseLigands(layout.get(fields, "ligand_comp_ids", "ligand"));
+
+ return new EcodDomain(uid, domainId, manual, xhtfGroup[0], xhtfGroup[1], xhtfGroup[2],
+ xhtfGroup[3], pdbId, chainId, range, seqId, architectureName, xGroupName,
+ hGroupName, tGroupName, fGroupName, assemblyId, ligands);
+ }
+
+ /**
+ * Reads the representative-status column, which held MANUAL_REP or AUTO_NONREP up to
+ * develop292 and True or False from v294.1 onwards.
+ * @return true, false, or null if the column is absent or unrecognised
+ * @since 7.3.0
+ */
+ private Boolean parseManualRep(String manualString, int lineNum) {
+ if(manualString == null) {
+ return null;
+ }
+ if(manualString.equalsIgnoreCase(IS_REPRESENTATIVE) || manualString.equalsIgnoreCase("true")) {
+ return true;
+ }
+ if(manualString.equalsIgnoreCase(NOT_REPRESENTATIVE) || manualString.equalsIgnoreCase("false")) {
+ return false;
+ }
+ logger.warn("Unexpected value for manual field: {} in line {}",manualString,lineNum);
+ return null;
+ }
+
+ /**
+ * Splits the hierarchical identifier, e.g. "1.1.4.1".
+ * @return the X, H, T and F group numbers, any of which may be null if absent
+ * @since 7.3.0
+ */
+ private Integer[] parseHierarchy(String hierarchy, int lineNum) {
+ String[] xhtGroup = hierarchy.split("\\.");
+ if(xhtGroup.length < 3 || 4 < xhtGroup.length) {
+ if(warnHierarchicalFormat > 1) {
+ logger.warn("Unexpected format for hierarchical field \"{}\" in line {}",hierarchy,lineNum);
+ warnHierarchicalFormat--;
+ } else if(warnHierarchicalFormat != 0) {
+ logger.warn("Unexpected format for hierarchical field \"{}\" in line {}. Not printing future similar warnings.",hierarchy,lineNum);
+ warnHierarchicalFormat--;
+ }
+ }
+ Integer[] groups = new Integer[4];
+ for(int j = 0; j < groups.length && j < xhtGroup.length; j++) {
+ groups[j] = Integer.parseInt(xhtGroup[j]);
+ }
+ return groups;
+ }
+
+ /**
+ * Reads a comma-separated list of non-polymer entities close to the domain.
+ * @return the ligands, or an empty set for NO_LIGANDS_4A, an empty value or no column
+ * @since 7.3.0
+ */
+ private Set parseLigands(String ligandStr) {
+ if( ligandStr == null || ligandStr.isEmpty() || "NO_LIGANDS_4A".equals(ligandStr) ) {
+ return Collections.emptySet();
+ }
+ String[] ligSplit = ligandStr.split(",");
+ Set ligands = new LinkedHashSet<>(ligSplit.length);
+ for(String s : ligSplit) {
+ ligands.add(s.intern());
+ }
+ return ligands;
+ }
+
+ /**
+ * Interns a name likely to be shared by many domains, stripping the quotes that
+ * versions up to develop292 wrapped some of them in.
+ * @since 7.3.0
+ */
+ private String internName(String name) {
+ if(name == null) {
+ return null;
+ }
+ return clearStringQuotes(name).intern();
+ }
+
+ private void warnDomainAssembly(int lineNum) {
+ if(warnIsDomainAssembly > 1) {
+ logger.info("Deprecated 'IS_DOMAIN_ASSEMBLY' value ignored in line {}.",lineNum);
+ warnIsDomainAssembly--;
+ } else if(warnIsDomainAssembly == 0) {
+ logger.info("Deprecated 'IS_DOMAIN_ASSEMBLY' value ignored in line {}. Not printing future similar warnings.",lineNum);
+ warnIsDomainAssembly--;
+ }
+ }
+
+ private void warnMissingColumns(int lineNum) {
+ if(warnNumberOfFields > 1) {
+ logger.warn("Unexpected number of fields in line {}.",lineNum);
+ warnNumberOfFields--;
+ } else if(warnNumberOfFields == 1) {
+ logger.warn("Unexpected number of fields in line {}. Not printing future similar warnings",lineNum);
+ warnNumberOfFields--;
+ }
+ }
+
+ private void warnUnparseableLine(int lineNum, IllegalArgumentException e) {
+ if(warnNumberFormat > 1) {
+ logger.warn("Error in ECOD parsing at line {}: {}", lineNum, e.getMessage());
+ warnNumberFormat--;
+ } else if(warnNumberFormat == 1) {
+ logger.warn("Error in ECOD parsing at line {}: {}. Not printing future similar warnings", lineNum, e.getMessage());
+ warnNumberFormat--;
+ }
+ }
+
private String clearStringQuotes(String name) {
if ( name.startsWith("\""))
name = name.substring(1);
@@ -770,6 +1064,77 @@ public List getDomains() {
public String getVersion() {
return version;
}
+
+ /**
+ * Maps the column names an ECOD domain file declares in its header onto their
+ * positions, so a data line can be read by name rather than by offset.
+ *
+ * Every distribution since develop101 carries such a header. It is commented
+ * (#uid<tab>ecod_domain_id<tab>...) up to develop292 and
+ * uncommented (uid<tab>ecod_domain_id<tab>...) from v294.1
+ * onwards. Because names have also been changed between versions, lookups accept
+ * aliases and any name the file does not declare simply reads as absent.
+ *
+ * @author Amr ALHOSSARY
+ * @since 7.3.0
+ */
+ private static class ColumnLayout {
+ private final Map columns;
+
+ private ColumnLayout(Map columns) {
+ this.columns = columns;
+ }
+
+ /**
+ * @return true if this line names the columns rather than holding domain data
+ */
+ public static boolean isColumnHeader(String line) {
+ int tab = line.indexOf('\t');
+ if(tab < 0) {
+ return false;
+ }
+ String first = line.substring(0, tab).trim();
+ if(first.startsWith("#")) {
+ first = first.substring(1).trim();
+ }
+ return first.equalsIgnoreCase("uid");
+ }
+
+ public static ColumnLayout fromHeader(String line) {
+ String[] names = line.split("\t", -1);
+ Map columns = new HashMap<>(names.length * 2);
+ for(int i = 0; i < names.length; i++) {
+ String name = names[i].trim();
+ if(i == 0 && name.startsWith("#")) {
+ name = name.substring(1).trim();
+ }
+ if(!name.isEmpty()) {
+ columns.put(name.toLowerCase(), i);
+ }
+ }
+ return new ColumnLayout(columns);
+ }
+
+ /**
+ * @param fields the values of one data line
+ * @param aliases the names this column has gone by, most recent first
+ * @return the value of the first alias the file declares, or null if it declares
+ * none of them or this line is too short to reach it
+ */
+ public String get(String[] fields, String... aliases) {
+ for(String alias : aliases) {
+ Integer i = columns.get(alias);
+ if(i != null) {
+ return i < fields.length ? fields[i] : null;
+ }
+ }
+ return null;
+ }
+
+ public int size() {
+ return columns.size();
+ }
+ }
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/geometry/MomentsOfInertia.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/geometry/MomentsOfInertia.java
index 8cfd032daa..5a6e69c4cf 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/geometry/MomentsOfInertia.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/geometry/MomentsOfInertia.java
@@ -72,7 +72,7 @@ public void addPoint(Point3d point, double mass) {
public Point3d getCenterOfMass() {
- if (points.size() == 0) {
+ if (points.isEmpty()) {
throw new IllegalStateException(
"MomentsOfInertia: no points defined");
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/BondMaker.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/BondMaker.java
index e6b8548025..e81f7fb867 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/BondMaker.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/BondMaker.java
@@ -273,7 +273,7 @@ private void trimBondLists() {
for (Chain chain : structure.getChains(modelInd)) {
for (Group group : chain.getAtomGroups()) {
for (Atom atom : group.getAtoms()) {
- if (atom.getBonds()!=null && atom.getBonds().size() > 0) {
+ if (atom.getBonds()!=null && !atom.getBonds().isEmpty()) {
((ArrayList) atom.getBonds()).trimToSize();
}
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java
index 4ec4577f59..2e6b09fe9a 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/LocalPDBDirectory.java
@@ -550,7 +550,7 @@ private File downloadStructure(PdbId pdbId, String pathOnServer, boolean obsolet
ftp = DEFAULT_BCIF_FILE_SERVER + filename;
} else {
ftp = String.format("%s%s/%s/%s",
- serverName, pathOnServer, id.substring(id.length()-3, id.length()-1), getFilename(id));
+ serverName, pathOnServer, getMiddleHash(id), getFilename(id));
}
URL url = new URL(ftp);
@@ -576,21 +576,45 @@ private File downloadStructure(PdbId pdbId, String pathOnServer, boolean obsolet
logger.info("Fetching {}", ftp);
logger.info("Writing to {}", realFile);
- FileDownloadUtils.createValidationFiles(url, realFile, null, FileDownloadUtils.Hash.UNKNOWN);
- FileDownloadUtils.downloadFile(url, realFile);
+ // A single connection, so the recorded size and checksum describe exactly the
+ // bytes that were written. The wwPDB servers return the content MD5 as the
+ // ETag, so this also gives the cached file a real integrity check.
+ FileDownloadUtils.downloadFileWithValidation(url, realFile, null, FileDownloadUtils.Hash.UNKNOWN,
+ FileDownloadUtils.ETagPolicy.USE_IF_HEX_DIGEST);
if(! FileDownloadUtils.validateFile(realFile))
throw new IOException("Downloaded file invalid: "+realFile);
return realFile;
}
+ /**
+ * Returns the two-character directory name under which an entry is filed in the
+ * PDB's divided layout, e.g. cb for 1cbs.
+ *
+ * The characters are taken relative to the end of the identifier rather
+ * than the start, so that both spellings of the same entry land in the same
+ * bucket: 1cbs and its extended form pdb_00001cbs both
+ * give cb. Taking them from the start would file the extended form
+ * under db instead. The extended PDB identifier format is expected
+ * to keep using this same hashing scheme.
+ *
+ * @param pdbId a PDB identifier, in either the short or the extended form
+ * @return the lowercase two-character directory name
+ * @since 7.3.0
+ */
+ public static String getMiddleHash(String pdbId) {
+ int offset = pdbId.length() - 3;
+ return pdbId.substring(offset, offset + 2).toLowerCase();
+ }
+
/**
* Get the last modified time of the file in given url by retrieveing the "Last-Modified" header.
* Note that this only works for http URLs
* @param url
* @return the last modified date or null if it couldn't be retrieved (in that case a warning will be logged)
+ * @since 7.3.0 made public so that other caching code can reuse it
*/
- private Date getLastModifiedTime(URL url) {
+ public static Date getLastModifiedTime(URL url) {
// see http://stackoverflow.com/questions/2416872/how-do-you-obtain-modified-date-from-a-remote-file-java
Date date = null;
@@ -629,14 +653,12 @@ private Date getLastModifiedTime(URL url) {
protected File getDir(String pdbId, boolean obsolete) {
File dir = null;
- int offset = pdbId.length() - 3;
+ String middle = getMiddleHash(pdbId);
if (obsolete) {
// obsolete is always split
- String middle = pdbId.substring(offset, offset + 2).toLowerCase();
dir = new File(obsoleteDirPath, middle);
} else {
- String middle = pdbId.substring(offset, offset + 2).toLowerCase();
dir = new File(splitDirPath, middle);
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/PDBFileParser.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/PDBFileParser.java
index 176459bbf2..b1d327599e 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/PDBFileParser.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/PDBFileParser.java
@@ -1406,7 +1406,7 @@ public void handleResolutionLine(String line, Pattern pR) {
try {
float res = Float.parseFloat(resString);
final float resInHeader = pdbHeader.getResolution();
- if (resInHeader!=PDBHeader.DEFAULT_RESOLUTION && resInHeader != res) {
+ if (resInHeader!=PDBHeader.DEFAULT_RESOLUTION && Math.abs(resInHeader - res) > 0.001) {
logger.warn("More than 1 resolution value present, will use last one {} and discard previous {} "
,resString, String.format("%4.2f",resInHeader));
}
@@ -1943,7 +1943,7 @@ private Group getCorrectAltLocGroup( Character altLoc,
// see if we know this altLoc already;
List atoms = currentGroup.getAtoms();
- if ( atoms.size() > 0) {
+ if (!atoms.isEmpty()) {
Atom a1 = atoms.get(0);
// we are just adding atoms to the current group
// probably there is a second group following later...
@@ -1956,7 +1956,7 @@ private Group getCorrectAltLocGroup( Character altLoc,
List altLocs = currentGroup.getAltLocs();
for ( Group altLocG : altLocs ){
atoms = altLocG.getAtoms();
- if ( atoms.size() > 0) {
+ if (!atoms.isEmpty()) {
for ( Atom a1 : atoms) {
if (a1.getAltLoc().equals( altLoc)) {
@@ -1970,7 +1970,7 @@ private Group getCorrectAltLocGroup( Character altLoc,
// build it up.
if ( groupCode3.equals(currentGroup.getPDBName())) {
- if ( currentGroup.getAtoms().size() == 0) {
+ if ( currentGroup.getAtoms().isEmpty()) {
//System.out.println("current group is empty " + current_group + " " + altLoc);
return currentGroup;
}
@@ -2762,7 +2762,7 @@ private void makeCompounds(List compoundList,
}
// System.out.println("[makeCompounds] adding sources to compounds from sourceLines");
// since we're starting again from the first compound, reset it here
- if ( entities.size() == 0){
+ if ( entities.isEmpty()){
current_compound = new EntityInfo();
} else {
current_compound = entities.get(0);
@@ -2921,7 +2921,7 @@ private void triggerEndFileChecks(){
pdbHeader.setBioAssemblies(bioAssemblyParser.getTransformationMap());
}
- if (ncsOperators !=null && ncsOperators.size()>0) {
+ if (ncsOperators !=null && !ncsOperators.isEmpty()) {
crystallographicInfo.setNcsOperators(
ncsOperators.toArray(new Matrix4d[ncsOperators.size()]));
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/SeqRes2AtomAligner.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/SeqRes2AtomAligner.java
index 7ee21de4b4..0652ad1809 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/SeqRes2AtomAligner.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/SeqRes2AtomAligner.java
@@ -198,7 +198,7 @@ public void mapSeqresRecords(Chain atomRes, Chain seqRes) {
}
}
- if ( atomRes.getAtomGroups(GroupType.AMINOACID).size() < 1) {
+ if (atomRes.getAtomGroups(GroupType.AMINOACID).isEmpty()) {
logger.debug("ATOM chain {} does not contain amino acids, ignoring...", atomRes.getId());
return;
}
@@ -215,7 +215,7 @@ public void mapSeqresRecords(Chain atomRes, Chain seqRes) {
private void alignNucleotideChains(Chain seqRes, Chain atomRes) {
- if ( atomRes.getAtomGroups(GroupType.NUCLEOTIDE).size() < 1) {
+ if (atomRes.getAtomGroups(GroupType.NUCLEOTIDE).isEmpty()) {
logger.debug("ATOM chain {} does not contain nucleotides, ignoring...", atomRes.getId());
return;
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/AbstractCifFileSupplier.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/AbstractCifFileSupplier.java
index 826d9588ef..e43565c827 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/AbstractCifFileSupplier.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/AbstractCifFileSupplier.java
@@ -40,8 +40,27 @@ protected CifFile getInternal(Structure structure, List wrappedAtom
// entity information
List entityInfos = structure.getEntityInfos();
+ PdbId pdbId = structure.getPdbId();
+
MmCifBlockBuilder blockBuilder = CifBuilder.enterFile(StandardSchemata.MMCIF)
- .enterBlock(structure.getPdbId() == null? "" : structure.getPdbId().getId());
+ .enterBlock(pdbId == null? "" : pdbId.getId());
+
+ if (pdbId != null) {
+ // The block header alone does not carry the identifier for consumers: readers pick it up from
+ // _entry.id (e.g. Jmol) or from _struct.entry_id (BioJava's own CifStructureConsumerImpl).
+ // Both are written so that the identifier survives a write-then-read round trip either way.
+ blockBuilder.enterEntry()
+ .enterId()
+ .add(pdbId.getId())
+ .leaveColumn()
+ .leaveCategory();
+
+ blockBuilder.enterStruct()
+ .enterEntryId()
+ .add(pdbId.getId())
+ .leaveColumn()
+ .leaveCategory();
+ }
blockBuilder.enterStructKeywords().enterText()
.add(String.join(", ", structure.getPDBHeader().getKeywords()))
@@ -308,7 +327,13 @@ public void accept(WrappedAtom wrappedAtom) {
}
}
labelEntityId.add(entityId);
- labelSeqId.add(seqId);
+ // see https://github.com/biojava/biojava/issues/1116
+ // note the first condition is to safeguard and to have a default that writes labelSeqId if there's no knowledge about what's the entity type
+ if (chain.getEntityInfo()==null || chain.getEntityInfo().getType() == EntityType.POLYMER) {
+ labelSeqId.add(seqId);
+ } else {
+ labelSeqId.markNextNotPresent();
+ }
String insCode = "";
if (group.getResidueNumber().getInsCode() != null) {
insCode = Character.toString(group.getResidueNumber().getInsCode());
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/CifStructureConsumerImpl.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/CifStructureConsumerImpl.java
index 67514edd84..f44eb44ab1 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/CifStructureConsumerImpl.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/cif/CifStructureConsumerImpl.java
@@ -372,7 +372,7 @@ public void consumeAtomSite(AtomSite atomSite) {
private Group getAltLocGroup(String recordName, Character altLoc, Character oneLetterCode, String threeLetterCode,
long seqId) {
List atoms = currentGroup.getAtoms();
- if (atoms.size() > 0) {
+ if (!atoms.isEmpty()) {
if (atoms.get(0).getAltLoc().equals(altLoc)) {
return currentGroup;
}
@@ -381,7 +381,7 @@ private Group getAltLocGroup(String recordName, Character altLoc, Character oneL
List altLocs = currentGroup.getAltLocs();
for (Group altLocGroup : altLocs) {
atoms = altLocGroup.getAtoms();
- if (atoms.size() > 0) {
+ if (!atoms.isEmpty()) {
for (Atom a1 : atoms) {
if (a1.getAltLoc().equals(altLoc)) {
return altLocGroup;
@@ -630,7 +630,8 @@ public void consumeDatabasePDBRev(DatabasePDBRev databasePDBrev) {
modDate = relDate;
} else {
String dbrev = databasePDBrev.getDate().get(rowIndex);
- modDate = convert(LocalDate.parse(dbrev, DATE_FORMAT));
+ if (dbrev != null && !dbrev.isBlank())
+ modDate = convert(LocalDate.parse(dbrev, DATE_FORMAT));
}
pdbHeader.setModDate(modDate);
}
@@ -855,7 +856,8 @@ public void consumeRefine(Refine refine) {
// we take the last one found so that behaviour is like in PDB file parsing
double lsDResHigh = refine.getLsDResHigh().get(rowIndex);
// TODO this could use a check to keep reasonable values - 1.5 may be overwritten by 0.0
- if (pdbHeader.getResolution() != PDBHeader.DEFAULT_RESOLUTION) {
+ if (pdbHeader.getResolution() != PDBHeader.DEFAULT_RESOLUTION &&
+ Math.abs(pdbHeader.getResolution() - lsDResHigh) > 0.001) {
logger.warn("More than 1 resolution value present, will use last one {} and discard previous {}",
lsDResHigh, String.format("%4.2f",pdbHeader.getResolution()));
}
@@ -1478,7 +1480,7 @@ private void setStructNcsOps() {
}
}
- if (ncsOperators.size() > 0) {
+ if (!ncsOperators.isEmpty()) {
structure.getCrystallographicInfo()
.setNcsOperators(ncsOperators.toArray(new Matrix4d[0]));
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmtf/MmtfStructureReader.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmtf/MmtfStructureReader.java
index c2830c1685..865c9e0da4 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmtf/MmtfStructureReader.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/io/mmtf/MmtfStructureReader.java
@@ -372,7 +372,7 @@ public void setInterGroupBond(int indOne, int indTwo, int bondOrder) {
private Group getCorrectAltLocGroup(Character altLoc) {
// see if we know this altLoc already;
List atoms = group.getAtoms();
- if (atoms.size() > 0) {
+ if (!atoms.isEmpty()) {
Atom a1 = atoms.get(0);
// we are just adding atoms to the current group
// probably there is a second group following later...
@@ -396,7 +396,7 @@ private Group getCorrectAltLocGroup(Character altLoc) {
}
// no matching altLoc group found.
// build it up.
- if (group.getAtoms().size() == 0) {
+ if (group.getAtoms().isEmpty()) {
return group;
}
Group altLocG = (Group) group.clone();
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BioAssemblyTools.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BioAssemblyTools.java
index 7c359121de..8f76b2ae61 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BioAssemblyTools.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BioAssemblyTools.java
@@ -55,7 +55,7 @@ public static boolean isUnaryExpression(String expression) {
if (first < 0 || last < 0) {
return true;
}
- return ! (first == 0 && last > first);
+ return first != 0 || last <= first;
}
public static List parseUnaryOperatorExpression(String operatorExpression) {
@@ -279,7 +279,7 @@ public static double[] getBiologicalMoleculeCentroid( final Structure asymUnit,
return centroid;
}
- if ( transformations.size() == 0) {
+ if ( transformations.isEmpty()) {
return Calc.getCentroid(atoms).getCoords();
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyBuilder.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyBuilder.java
index c6ec6bc8ff..9edb8f404c 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyBuilder.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyBuilder.java
@@ -205,7 +205,7 @@ private void addChainMultiModel(Structure s, Chain newChain, String transformId)
// multi-model bioassembly
- if ( modelIndex.size() == 0)
+ if (modelIndex.isEmpty())
modelIndex.add("PLACEHOLDER FOR ASYM UNIT");
int modelCount = modelIndex.indexOf(transformId);
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyTransformation.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyTransformation.java
index 36bccd7b39..ff1efd6e32 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyTransformation.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/quaternary/BiologicalAssemblyTransformation.java
@@ -226,7 +226,7 @@ public static BiologicalAssemblyTransformation fromXML(String xml)
List transformations = fromMultiXML(xml);
- if ( transformations.size() > 0)
+ if (!transformations.isEmpty())
return transformations.get(0);
else
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/scop/ScopInstallation.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/scop/ScopInstallation.java
index d092d5485e..4ac55cfa08 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/scop/ScopInstallation.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/scop/ScopInstallation.java
@@ -655,7 +655,7 @@ private List extractRanges(String range) {
}
protected void downloadClaFile() throws IOException{
- if(mirrors.size()<1) {
+ if(mirrors.isEmpty()) {
initScopURLs();
}
IOException exception = null;
@@ -676,7 +676,7 @@ protected void downloadClaFile() throws IOException{
}
protected void downloadDesFile() throws IOException{
- if(mirrors.size()<1) {
+ if(mirrors.isEmpty()) {
initScopURLs();
}
IOException exception = null;
@@ -697,7 +697,7 @@ protected void downloadDesFile() throws IOException{
}
protected void downloadHieFile() throws IOException{
- if(mirrors.size()<1) {
+ if(mirrors.isEmpty()) {
initScopURLs();
}
IOException exception = null;
@@ -719,7 +719,7 @@ protected void downloadHieFile() throws IOException{
}
protected void downloadComFile() throws IOException{
- if(mirrors.size()<1) {
+ if(mirrors.isEmpty()) {
initScopURLs();
}
IOException exception = null;
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/secstruc/SecStrucTools.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/secstruc/SecStrucTools.java
index 7732c04b80..42191d682d 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/secstruc/SecStrucTools.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/secstruc/SecStrucTools.java
@@ -57,7 +57,7 @@ public static List getSecStrucInfo(Structure s) {
Group g = iter.next();
if (g.hasAminoAtoms()) {
Object p = g.getProperty(Group.SEC_STRUC);
- if (!(p == null)) {
+ if (p != null) {
SecStrucInfo ss = (SecStrucInfo) p;
listSSI.add(ss);
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/HelicalRepeatUnit.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/HelicalRepeatUnit.java
index ff9c77cf13..cd16aa5f87 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/HelicalRepeatUnit.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/HelicalRepeatUnit.java
@@ -64,7 +64,7 @@ public Map getInteractingRepeatUnits() {
private void run() {
this.repeatUnitCenters = calcRepeatUnitCenters();
- if (this.repeatUnitCenters.size() == 0) {
+ if (this.repeatUnitCenters.isEmpty()) {
return;
}
this.repeatUnits = calcRepeatUnits();
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/PermutationGroup.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/PermutationGroup.java
index e1f4792410..b3ac53f385 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/PermutationGroup.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/PermutationGroup.java
@@ -65,7 +65,7 @@ public void completeGroup() {
Set> known = new HashSet<>(permutations);
//breadth-first search through the map of all members
List> currentLevel = new ArrayList<>(permutations);
- while( currentLevel.size() > 0) {
+ while(!currentLevel.isEmpty()) {
List> nextLevel = new ArrayList<>();
for( List p : currentLevel) {
for(List gen : gens) {
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/QuatSymmetrySubunits.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/QuatSymmetrySubunits.java
index b0bef7f1e6..7f434cabaf 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/QuatSymmetrySubunits.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/QuatSymmetrySubunits.java
@@ -211,7 +211,7 @@ public MomentsOfInertia getMomentsOfInertia() {
}
private void run() {
- if (centers.size() > 0) {
+ if (!centers.isEmpty()) {
return;
}
calcOriginalCenters();
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationGroup.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationGroup.java
index 70b69afe14..002d046e52 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationGroup.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationGroup.java
@@ -83,7 +83,7 @@ public void removeRotation(int index) {
public void complete() {
if (modified) {
- if (rotations.size() > 0) {
+ if (!rotations.isEmpty()) {
findHighestOrderAxis();
setEAxis();
calcAxesDirections();
@@ -98,7 +98,7 @@ public void complete() {
public String getPointGroup() {
if (modified) {
- if (rotations.size() == 0) {
+ if (rotations.isEmpty()) {
return "C1";
}
complete();
@@ -344,7 +344,7 @@ private void calcPointGroup() {
// when a structure is symmetric, some subunits are below the rmsd threshold,
// and some are just above the rmsd threshold
int n = 0;
- if (rotations.size() > 0) {
+ if (!rotations.isEmpty()) {
n = rotations.get(0).getPermutation().size();
rotations.clear();
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationSolver.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationSolver.java
index 37a44be7ac..b1566a6d2f 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationSolver.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/RotationSolver.java
@@ -305,7 +305,7 @@ private boolean isSpherical() {
* @return null if invalid, or a rotation if valid
*/
private Rotation isValidPermutation(List permutation) {
- if (permutation.size() == 0) {
+ if (permutation.isEmpty()) {
return null;
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/SystematicSolver.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/SystematicSolver.java
index d13fa4db16..a449771b58 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/SystematicSolver.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/core/SystematicSolver.java
@@ -145,7 +145,7 @@ private void completeRotationGroup() {
}
private boolean isValidPermutation(List permutation) {
- if (permutation.size() == 0) {
+ if (permutation.isEmpty()) {
return false;
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/geometry/DistanceBox.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/geometry/DistanceBox.java
index 2d9b1d6dca..25d37693fb 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/geometry/DistanceBox.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/geometry/DistanceBox.java
@@ -166,7 +166,7 @@ private List getBoxTwo(long location) {
}
// ensure that boxTwo has no empty element by copying from tempBox of defined size
List boxTwo = null;
- if (tempBox.size() == 0) {
+ if (tempBox.isEmpty()) {
boxTwo = Collections.emptyList();
} else if (tempBox.size() == 1) {
boxTwo = Collections.singletonList(tempBox.get(0));
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SequenceFunctionRefiner.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SequenceFunctionRefiner.java
index a6ad226698..0d52e92fef 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SequenceFunctionRefiner.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SequenceFunctionRefiner.java
@@ -79,7 +79,7 @@ public static AFPChain refineSymmetry(AFPChain afpChain, Atom[] ca1, Atom[] ca2,
// Refine the alignment Map
Map refined = refineSymmetry(alignment, k);
- if (refined.size() < 1)
+ if (refined.isEmpty())
throw new RefinerFailedException("Refiner returned empty alignment");
//Substitute and partition the alignment
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SymmOptimizer.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SymmOptimizer.java
index d03e90080f..627908ac8b 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SymmOptimizer.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/internal/SymmOptimizer.java
@@ -392,10 +392,7 @@ private boolean checkGaps() {
length--;
}
- if (shrinkColumns.size() != 0)
- return true;
- else
- return false;
+ return !shrinkColumns.isEmpty();
}
/**
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/utils/BlastClustReader.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/utils/BlastClustReader.java
index b2b3298157..5d1faa8754 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/utils/BlastClustReader.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/symmetry/utils/BlastClustReader.java
@@ -140,7 +140,7 @@ public List> getChainIdsInEntry(String pdbId) {
private void loadClusters(int sequenceIdentity) {
// load clusters only once
- if (clusters.size() > 0) {
+ if (!clusters.isEmpty()) {
return;
}
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/xtal/SpaceGroup.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/xtal/SpaceGroup.java
index cff84c70f8..852d213bb9 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/xtal/SpaceGroup.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/xtal/SpaceGroup.java
@@ -645,10 +645,10 @@ public List getTransfAlgebraic() {
public void setTransfAlgebraic(List transfAlgebraic) {
//System.out.println("setting transfAlgebraic " + transfAlgebraic);
- if ( transformations == null || transformations.size() == 0)
+ if ( transformations == null || transformations.isEmpty())
transformations = new ArrayList(transfAlgebraic.size());
- if ( this.transfAlgebraic == null || this.transfAlgebraic.size() == 0)
+ if ( this.transfAlgebraic == null || this.transfAlgebraic.isEmpty())
this.transfAlgebraic = new ArrayList<>(transfAlgebraic.size());
for ( String transf : transfAlgebraic){
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java
index 073a679dbb..f2f06ed2f5 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/align/util/AtomCacheTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import java.io.File;
@@ -408,7 +409,7 @@ public void testEmptyChemComp() throws IOException, StructureException {
// should be unknown
ChemComp chem = g.getChemComp();
assertNotNull(chem);
- assertTrue(chem.getAtoms().size() > 0);
+ assertFalse(chem.getAtoms().isEmpty());
assertEquals("NON-POLYMER", chem.getType());
} finally {
FileDownloadUtils.deleteDirectory(tmpCache);
@@ -471,7 +472,7 @@ public void testEmptyGZChemComp() throws IOException, StructureException {
// should be unknown
ChemComp chem = g.getChemComp();
assertNotNull(chem);
- assertTrue(chem.getAtoms().size() > 0);
+ assertFalse(chem.getAtoms().isEmpty());
assertEquals("NON-POLYMER", chem.getType());
} finally {
FileDownloadUtils.deleteDirectory(tmpCache);
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java
index 7be56be156..c6a268f1f5 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java
@@ -126,19 +126,19 @@ public void testNeighborIndicesFinding() throws StructureException, IOException
AsaCalculator.DEFAULT_PROBE_SIZE,
1000, 1, false);
- AsaCalculator.IndexAndDistance[][] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
+ AsaCalculator.Neighbors[] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
- AsaCalculator.IndexAndDistance[][] allNbs = asaCalc.findNeighborIndices();
+ AsaCalculator.Neighbors[] allNbs = asaCalc.findNeighborIndices();
for (int indexToTest =0; indexToTest < asaCalc.getAtomCoords().length; indexToTest++) {
//int indexToTest = 198;
- AsaCalculator.IndexAndDistance[] nbsSh = allNbsSh[indexToTest];
- AsaCalculator.IndexAndDistance[] nbs = allNbs[indexToTest];
+ int[] nbsSh = allNbsSh[indexToTest].indices;
+ int[] nbs = allNbs[indexToTest].indices;
List listOfMatchingIndices = new ArrayList<>();
for (int i = 0; i < nbsSh.length; i++) {
for (int j = 0; j < nbs.length; j++) {
- if (nbs[j].index == nbsSh[i].index) {
+ if (nbs[j] == nbsSh[i]) {
listOfMatchingIndices.add(j);
break;
}
@@ -229,21 +229,21 @@ public void testNoNeighborsIssue() {
AsaCalculator.DEFAULT_PROBE_SIZE,
1000, 1);
- AsaCalculator.IndexAndDistance[][] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
+ AsaCalculator.Neighbors[] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
- AsaCalculator.IndexAndDistance[][] allNbs = asaCalc.findNeighborIndices();
+ AsaCalculator.Neighbors[] allNbs = asaCalc.findNeighborIndices();
assertEquals(3, allNbs.length);
assertEquals(3, allNbsSh.length);
for (int indexToTest =0; indexToTest < asaCalc.getAtomCoords().length; indexToTest++) {
- AsaCalculator.IndexAndDistance[] nbsSh = allNbsSh[indexToTest];
- AsaCalculator.IndexAndDistance[] nbs = allNbs[indexToTest];
+ int[] nbsSh = allNbsSh[indexToTest].indices;
+ int[] nbs = allNbs[indexToTest].indices;
List listOfMatchingIndices = new ArrayList<>();
for (int i = 0; i < nbsSh.length; i++) {
for (int j = 0; j < nbs.length; j++) {
- if (nbs[j].index == nbsSh[i].index) {
+ if (nbs[j] == nbsSh[i]) {
listOfMatchingIndices.add(j);
break;
}
@@ -256,7 +256,7 @@ public void testNoNeighborsIssue() {
}
// first atom should have no neighbors
- assertEquals(0, allNbsSh[0].length);
+ assertEquals(0, allNbsSh[0].indices.length);
}
private Atom getAtom(double x, double y, double z) {
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/cath/CathInstallationTest.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/cath/CathInstallationTest.java
new file mode 100644
index 0000000000..69944cb51c
--- /dev/null
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/cath/CathInstallationTest.java
@@ -0,0 +1,87 @@
+/*
+ * BioJava development code
+ *
+ * This code may be freely distributed and modified under the
+ * terms of the GNU Lesser General Public Licence. This should
+ * be distributed with the code. If you do not have a copy,
+ * see:
+ *
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * Copyright for this code is held jointly by the individual
+ * authors. These should be listed in @author doc comments.
+ *
+ * For more information on the BioJava project and its aims,
+ * or to join the biojava-l mailing list, visit the home page
+ * at:
+ *
+ * http://www.biojava.org/
+ */
+package org.biojava.nbio.structure.cath;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class CathInstallationTest {
+
+ @Test
+ public void testParseCathDomainListSuccess() throws IOException {
+ String data = "# CATH domain list\n"
+ + "\n"
+ + "1oaiA00 1 10 490 10 1 1 1 1 1 124 1.80\n"
+ + "1oaiA01 1 10 490 10 1 1 1 1 2 150 1.80\n";
+
+ CathInstallation installation = new CathInstallation("");
+ BufferedReader reader = new BufferedReader(new StringReader(data));
+ installation.parseCathDomainList(reader);
+ installation.setInstalledDomainList(new AtomicBoolean(true)); //1
+ installation.setInstalledDomall(new AtomicBoolean(true)); //2
+
+ CathDomain domain = installation.getDomainByCathId("1oaiA00");
+ assertNotNull(domain);
+ assertEquals("1oaiA00", domain.getDomainName());
+ assertEquals(1, domain.getClassId());
+ assertEquals(10, domain.getArchitectureId());
+ assertEquals(490, domain.getTopologyId());
+ assertEquals(10, domain.getHomologyId());
+ assertEquals(124, domain.getLength());
+ assertEquals(1.80, domain.getResolution(), 0.001);
+ }
+
+ @Test
+ public void testParseCathDomainListEmptyThrowsException() {
+ CathInstallation installation = new CathInstallation("");
+ BufferedReader reader = new BufferedReader(new StringReader(""));
+ assertThrows(IOException.class, () -> installation.parseCathDomainList(reader));
+ }
+
+ @Test
+ public void testParseCathDomainListOnlyCommentsAndWhitespaceThrowsException() {
+ String data = "# comment 1\n"
+ + "# comment 2\n"
+ + " \n"
+ + "\t\n";
+ CathInstallation installation = new CathInstallation("");
+ BufferedReader reader = new BufferedReader(new StringReader(data));
+ assertThrows(IOException.class, () -> installation.parseCathDomainList(reader));
+ }
+
+ @Test
+ public void testParseCathDomainListNoParsableLinesThrowsException() {
+ String data = "# comment\n"
+ + "invalid line with too few tokens\n"
+ + "another bad line\n";
+ CathInstallation installation = new CathInstallation("");
+ BufferedReader reader = new BufferedReader(new StringReader(data));
+ IOException exception = assertThrows(IOException.class, () -> installation.parseCathDomainList(reader));
+ assertNotNull(exception.getMessage());
+ }
+}
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/chem/TestChemCompRedirectNotCached.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/chem/TestChemCompRedirectNotCached.java
new file mode 100644
index 0000000000..b948e9fbfb
--- /dev/null
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/chem/TestChemCompRedirectNotCached.java
@@ -0,0 +1,154 @@
+/**
+ * BioJava development code
+ *
+ * This code may be freely distributed and modified under the terms of the GNU
+ * Lesser General Public Licence. This should be distributed with the code. If
+ * you do not have a copy, see:
+ *
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * Copyright for this code is held jointly by the individual authors. These
+ * should be listed in @author doc comments.
+ *
+ * For more information on the BioJava project and its aims, or to join the
+ * biojava-l mailing list, visit the home page at:
+ *
+ * http://www.biojava.org/
+ */
+package org.biojava.nbio.structure.chem;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.net.httpserver.HttpServer;
+
+import org.biojava.nbio.core.util.FlatFileCache;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * A server that redirects, or errors, must not have its response body cached as
+ * though it were a chemical component definition.
+ *
+ * This is the failure that took the CATH downloader out when
+ * download.cathdb.info moved to https, and the chem comp download had
+ * the same shape. A 4xx already failed safely, because
+ * getInputStream() throws for those; a redirect did not, because when
+ * the JDK declines to follow a 3xx it hands back the redirect's body instead, and
+ * that body is short but not empty.
+ *
+ * The test serves the responses from a local {@link HttpServer} rather than a real
+ * service. Pointing it at a third-party server that happens to redirect today would
+ * make the test fail on the day they stop, which is precisely the coupling that made
+ * the build unreliable in the first place.
+ *
+ * @author Amr ALHOSSARY
+ * @since 7.3.0
+ */
+public class TestChemCompRedirectNotCached {
+
+ private HttpServer server;
+ private String originalServerUrl;
+
+ @Before
+ public void setUp() {
+ originalServerUrl = DownloadChemCompProvider.serverBaseUrl;
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ server.stop(0);
+ }
+ // Static state: leaving either of these set would corrupt unrelated tests.
+ DownloadChemCompProvider.serverBaseUrl = originalServerUrl;
+ FlatFileCache.clear();
+ }
+
+ /**
+ * Starts a local server that answers every request with the given status and body.
+ *
+ * @return the base URL to point the provider at
+ */
+ private String startServer(int status, String location, String body) throws IOException {
+ server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+ server.createContext("/", exchange -> {
+ byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
+ if (location != null) {
+ exchange.getResponseHeaders().add("Location", location);
+ }
+ exchange.sendResponseHeaders(status, bytes.length);
+ try (OutputStream out = exchange.getResponseBody()) {
+ out.write(bytes);
+ }
+ });
+ server.start();
+ return "http://127.0.0.1:" + server.getAddress().getPort() + "/";
+ }
+
+ private File cacheFileFor(String id) {
+ File file = new File(DownloadChemCompProvider.getLocalFileName(id));
+ file.delete();
+ FlatFileCache.clear();
+ return file;
+ }
+
+ /**
+ * The case that broke CATH: a redirect the JDK will not follow because it
+ * changes protocol. Its body must not end up on disk under the component's name.
+ */
+ @Test
+ public void redirectBodyIsNotCached() throws IOException {
+ File cached = cacheFileFor("ATP");
+ DownloadChemCompProvider.serverBaseUrl =
+ startServer(301, "https://example.invalid/ATP.cif", "Moved Permanently");
+
+ ChemComp cc = new DownloadChemCompProvider().getChemComp("ATP");
+
+ assertFalse("the body of a redirect must never be cached as a definition", cached.exists());
+ assertNull("nothing parseable was returned, so the component must be empty", cc.getName());
+ }
+
+ /**
+ * A 200 is still cached, so the guard has not simply disabled downloading.
+ *
+ * What is under test is the download path, not the CIF parser: the response is
+ * written to the cache before anything tries to parse it, so a parse failure on
+ * this deliberately minimal body says nothing about whether the guard behaved.
+ */
+ @Test
+ public void aValidResponseIsStillCached() throws IOException {
+ File cached = cacheFileFor("ATP");
+ DownloadChemCompProvider.serverBaseUrl = startServer(200, null,
+ "data_ATP\n#\n_chem_comp.id ATP\n_chem_comp.name \"ADENOSINE-5'-TRIPHOSPHATE\"\n#\n");
+
+ try {
+ new DownloadChemCompProvider().getChemComp("ATP");
+ } catch (RuntimeException parseFailure) {
+ // see the note above
+ }
+
+ assertTrue("a 200 response should still be cached", cached.exists());
+ cached.delete();
+ }
+
+ /** A server error must not be cached either. */
+ @Test
+ public void serverErrorBodyIsNotCached() throws IOException {
+ File cached = cacheFileFor("ATP");
+ DownloadChemCompProvider.serverBaseUrl =
+ startServer(503, null, "Service Unavailable");
+
+ new DownloadChemCompProvider().getChemComp("ATP");
+
+ assertFalse("the body of a 5xx must never be cached as a definition", cached.exists());
+ }
+}
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/ecod/EcodParserTest.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/ecod/EcodParserTest.java
new file mode 100644
index 0000000000..295806e915
--- /dev/null
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/ecod/EcodParserTest.java
@@ -0,0 +1,323 @@
+/*
+ * BioJava development code
+ *
+ * This code may be freely distributed and modified under the
+ * terms of the GNU Lesser General Public Licence. This should
+ * be distributed with the code. If you do not have a copy,
+ * see:
+ *
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * Copyright for this code is held jointly by the individual
+ * authors. These should be listed in @author doc comments.
+ *
+ * For more information on the BioJava project and its aims,
+ * or to join the biojava-l mailing list, visit the home page
+ * at:
+ *
+ * http://www.biojava.org/
+ */
+package org.biojava.nbio.structure.ecod;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.biojava.nbio.structure.ecod.EcodInstallation.EcodParser;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Checks that {@link EcodParser} reads every layout ECOD has distributed.
+ *
+ * The columns have been renamed, reordered and added to several times, and the version
+ * comment itself changed form at v294.1. Because the full distribution is 657 MB, none of
+ * that was covered by a test that could run in reasonable time, and a format change went
+ * unnoticed for months. These fixtures are taken verbatim from the real files, so the
+ * contract is pinned in milliseconds rather than by a download.
+ *
+ * @author Amr ALHOSSARY
+ * @since 7.3.0
+ */
+class EcodParserTest {
+
+ /** develop204, list format 1.5: 15 columns, commented header, quoted names. */
+ private static final String DEVELOP204 = String.join("\n",
+ "#/data/ecod/database_versions/v204/ecod.develop204.domains.txt",
+ "#ECOD version develop204",
+ "#Domain list version 1.5",
+ "#Grishin lab (http://prodata.swmed.edu/ecod)",
+ "#uid\tecod_domain_id\tmanual_rep\tf_id\tpdb\tchain\tpdb_range\tseqid_range"
+ + "\tarch_name\tx_name\th_name\tt_name\tf_name\tasm_status\tligand",
+ "002137905\te6b4nA1\tAUTO_NONREP\t1.1.1\t6b4n\tA\tA:1-99\tA:1-99\tbeta barrels"
+ + "\t\"cradle loop barrel\"\t\"RIFT-related\"\t\"acid protease\""
+ + "\tF_UNCLASSIFIED\tNOT_DOMAIN_ASSEMBLY\tCL,G53,NA");
+
+ /** develop291, list format 1.6: 16 columns, f_id renamed t_id, unp_acc inserted at 9. */
+ private static final String DEVELOP291 = String.join("\n",
+ "#/data/ecod/database_versions/v291/ecod.develop291.domains.txt",
+ "#ECOD version develop291",
+ "#Domain list version 1.6",
+ "#Grishin lab (http://prodata.swmed.edu/ecod)",
+ "#uid\tecod_domain_id\tmanual_rep\tt_id\tpdb\tchain\tpdb_range\tseqid_range\tunp_acc"
+ + "\tarch_name\tx_name\th_name\tt_name\tf_name\tasm_status\tligand",
+ "000000267\te1udzA1\tMANUAL_REP\t1.1.1\t1udz\tA\tA:203-381\tA:4-182\tP12345"
+ + "\tbeta barrels\t\"cradle loop barrel\"\t\"RIFT-related\"\t\"acid protease\""
+ + "\tF_UNCLASSIFIED\tNOT_DOMAIN_ASSEMBLY\tNO_LIGANDS_4A");
+
+ private static final String V295_COLUMNS =
+ "uid\tecod_domain_id\tmanual_rep\tf_id\tpdb\tchain\tpdb_range\tseqid_range"
+ + "\tarchitecture_name\tx_name\th_name\tt_name\tf_name\tassembly_id\tdomain_id_short"
+ + "\trange_count\tarch_manual\tx_manual\th_manual\tt_manual\tf_manual"
+ + "\tvalid_structure\tligand_binding\tligand_comp_ids\tligand_pdbnum";
+
+ /** v295: 25 columns, uncommented header, True/False, empty assembly_id, moved ligands. */
+ private static final String V295 = String.join("\n",
+ "# ECOD Domain List",
+ "# Version: v295",
+ "# Generated: 2026-06-24 22:47:42",
+ "# Ligand cutoff: 4.0 A (NO_LIGANDS_4A = no contact within cutoff)",
+ "#",
+ V295_COLUMNS,
+ "0\te2nmzA1\tTrue\t1.1.1.3\t2nmz\tA\tA:1-99\tA:1-99\tbeta barrels\tcradle loop barrel"
+ + "\tRIFT-related\tacid protease\tRVP\t\t\t1\tFalse\tFalse\tFalse\tFalse\tTrue"
+ + "\tTrue\tTrue\tROC,SO4\tA:601,A:602,B:401",
+ // the last column is empty on four rows in five, so split() must keep it
+ "3\te2rspA1\tTrue\t1.1.1.3\t2rsp\tA\tA:1-124\tA:1-124\tbeta barrels\tcradle loop barrel"
+ + "\tRIFT-related\tacid protease\tRVP\t\t\t1\tFalse\tFalse\tFalse\tFalse\tTrue"
+ + "\tTrue\tFalse\tNO_LIGANDS_4A\t",
+ // a domain classified from an AlphaFold model: no PDB entry, so no EcodDomain
+ "3163557\tP44140_F1_nD2\tFalse\t2004.1.1.123\t\t\t131-315\t131-315\talpha bundles"
+ + "\tsomething\tsomething else\ta third thing\t\t\t\t1\tFalse\tFalse\tFalse"
+ + "\tFalse\tTrue\tTrue\tFalse\tNO_LIGANDS_4A\t");
+
+ private static List parse(String contents) throws IOException {
+ return new EcodParser(new StringReader(contents)).getDomains();
+ }
+
+ private static String version(String contents) throws IOException {
+ return new EcodParser(new StringReader(contents)).getVersion();
+ }
+
+ @Nested
+ class Version {
+ @Test
+ void oldHeaderForm() throws IOException {
+ assertEquals("develop204", version(DEVELOP204));
+ assertEquals("develop291", version(DEVELOP291));
+ }
+
+ @Test
+ void newHeaderForm() throws IOException {
+ assertEquals("v295", version(V295));
+ assertEquals("v294.1", version("# ECOD Domain List\n# Version: v294.1\n"));
+ }
+
+ @Test
+ void listFormatVersionIsNotTheEcodVersion() throws IOException {
+ // "#Domain list version 1.5" describes the columns, not the release
+ assertNull(version("#Grishin lab\n#Domain list version 1.5\n"));
+ }
+
+ @Test
+ void absentVersionIsNull() throws IOException {
+ assertNull(version("#Grishin lab (http://prodata.swmed.edu/ecod)\n"));
+ }
+ }
+
+ @Nested
+ class OldFormats {
+ @Test
+ void listFormat15() throws IOException {
+ List domains = parse(DEVELOP204);
+ assertEquals(1, domains.size());
+ EcodDomain d = domains.get(0);
+ assertEquals(Long.valueOf(2137905), d.getUid());
+ assertEquals("e6b4nA1", d.getDomainId());
+ assertEquals(Boolean.FALSE, d.getManual());
+ assertEquals(Integer.valueOf(1), d.getXGroup());
+ assertEquals(Integer.valueOf(1), d.getHGroup());
+ assertEquals(Integer.valueOf(1), d.getTGroup());
+ assertNull(d.getFGroup());
+ assertEquals("6B4N", d.getPdbId().getId());
+ assertEquals("A", d.getChainId());
+ assertEquals("A:1-99", d.getRange());
+ assertEquals("A:1-99", d.getSeqIdRange());
+ assertEquals("beta barrels", d.getArchitectureName());
+ // quotes were stripped up to develop292
+ assertEquals("cradle loop barrel", d.getXGroupName());
+ assertEquals("RIFT-related", d.getHGroupName());
+ assertEquals("acid protease", d.getTGroupName());
+ assertEquals("F_UNCLASSIFIED", d.getFGroupName());
+ assertEquals(Long.valueOf(2137905), d.getAssemblyId());
+ assertEquals(new LinkedHashSet<>(Arrays.asList("CL", "G53", "NA")), d.getLigands());
+ }
+
+ /**
+ * develop291 inserted unp_acc before arch_name. Read positionally, every field from
+ * there on shifts by one and the domain is silently mangled or dropped.
+ */
+ @Test
+ void listFormat16InsertsUnpAcc() throws IOException {
+ List domains = parse(DEVELOP291);
+ assertEquals(1, domains.size());
+ EcodDomain d = domains.get(0);
+ assertEquals(Boolean.TRUE, d.getManual());
+ assertEquals("1UDZ", d.getPdbId().getId());
+ assertEquals("A:4-182", d.getSeqIdRange());
+ assertEquals("beta barrels", d.getArchitectureName());
+ assertEquals("acid protease", d.getTGroupName());
+ assertEquals("F_UNCLASSIFIED", d.getFGroupName());
+ assertEquals(Long.valueOf(267), d.getAssemblyId());
+ assertEquals(Collections.emptySet(), d.getLigands());
+ }
+
+ /**
+ * Headers were only added in develop101. Older files are still read by position.
+ */
+ @Test
+ void thirteenColumnsWithoutAHeader() throws IOException {
+ List domains = parse(String.join("\n",
+ "#ECOD version develop45",
+ "000000001\te1udzA1\t1.1.1\t1udz\tA\tA:203-381\tbeta barrels"
+ + "\t\"cradle loop barrel\"\t\"RIFT-related\"\t\"acid protease\""
+ + "\tF_UNCLASSIFIED\tNOT_DOMAIN_ASSEMBLY\tNO_LIGANDS_4A"));
+ assertEquals(1, domains.size());
+ EcodDomain d = domains.get(0);
+ assertNull(d.getManual(), "no manual_rep column before list format 1.1");
+ assertNull(d.getSeqIdRange(), "no seqid_range column before list format 1.4");
+ assertEquals("1UDZ", d.getPdbId().getId());
+ assertEquals("acid protease", d.getTGroupName());
+ }
+ }
+
+ @Nested
+ class NewFormat {
+ @Test
+ void twentyFiveColumns() throws IOException {
+ List domains = parse(V295);
+ // two PDB domains; the AlphaFold-derived row cannot be an EcodDomain
+ assertEquals(2, domains.size());
+
+ EcodDomain d = domains.get(0);
+ assertEquals(Long.valueOf(0), d.getUid());
+ assertEquals("e2nmzA1", d.getDomainId());
+ assertEquals(Boolean.TRUE, d.getManual(), "manual_rep is now True/False");
+ assertEquals(Integer.valueOf(1), d.getXGroup());
+ assertEquals(Integer.valueOf(1), d.getHGroup());
+ assertEquals(Integer.valueOf(1), d.getTGroup());
+ assertEquals(Integer.valueOf(3), d.getFGroup(), "f_id now carries a fourth level");
+ assertEquals("2NMZ", d.getPdbId().getId());
+ assertEquals("A", d.getChainId());
+ assertEquals("A:1-99", d.getRange());
+ assertEquals("A:1-99", d.getSeqIdRange());
+ assertEquals("beta barrels", d.getArchitectureName());
+ assertEquals("cradle loop barrel", d.getXGroupName());
+ assertEquals("RIFT-related", d.getHGroupName());
+ assertEquals("acid protease", d.getTGroupName());
+ assertEquals("RVP", d.getFGroupName());
+ // assembly_id is empty on every row of v294.1 and later, which means the same
+ // as the NOT_DOMAIN_ASSEMBLY of earlier versions
+ assertEquals(Long.valueOf(0), d.getAssemblyId());
+ assertEquals(new LinkedHashSet<>(Arrays.asList("ROC", "SO4")), d.getLigands(),
+ "the ligand list moved to ligand_comp_ids");
+ }
+
+ /**
+ * ligand_pdbnum, the last column, is empty on four rows in five. String.split
+ * discards trailing empty fields unless asked not to, which would make those rows
+ * look one column short.
+ */
+ @Test
+ void rowEndingInAnEmptyColumn() throws IOException {
+ EcodDomain d = parse(V295).get(1);
+ assertEquals("e2rspA1", d.getDomainId());
+ assertEquals("2RSP", d.getPdbId().getId());
+ assertEquals(Collections.emptySet(), d.getLigands());
+ }
+
+ @Test
+ void twentyThreeColumnsOfV2941() throws IOException {
+ List domains = parse(String.join("\n",
+ "# ECOD Domain List",
+ "# Version: v294.1",
+ "#",
+ "uid\tecod_domain_id\tmanual_rep\tf_id\tpdb\tchain\tpdb_range\tseqid_range"
+ + "\tarchitecture_name\tx_name\th_name\tt_name\tf_name\tassembly_id"
+ + "\tdomain_id_short\trange_count\tarch_manual\tx_manual\th_manual"
+ + "\tt_manual\tf_manual\tvalid_structure\tligand_binding",
+ "1\te1hvcA1\tFalse\t1.1.1.3\t1hvc\tA\tA:1B-99A\tA:1-203\tbeta barrels"
+ + "\tcradle loop barrel\tRIFT-related\tacid protease\tRVP\t\t\t\tFalse"
+ + "\tFalse\tFalse\tFalse\tFalse\tTrue\tFalse"));
+ assertEquals(1, domains.size());
+ EcodDomain d = domains.get(0);
+ assertEquals("1HVC", d.getPdbId().getId());
+ assertEquals("A:1B-99A", d.getRange());
+ assertEquals("RVP", d.getFGroupName());
+ // there is no ligand column at all in v294.1
+ assertEquals(Collections.emptySet(), d.getLigands());
+ }
+
+ @Test
+ void columnHeaderIsNotADomain() throws IOException {
+ // v294.1 stopped commenting the column names out, so they arrive looking like data
+ for (EcodDomain d : parse(V295)) {
+ assertFalse("uid".equals(d.getDomainId()));
+ }
+ }
+
+ /**
+ * An empty f_name is not the same as F_UNCLASSIFIED: f_id still classifies the
+ * domain to four levels, so the empty value is left as it is rather than translated.
+ */
+ @Test
+ void emptyFGroupNameIsLeftAlone() throws IOException {
+ List domains = parse(String.join("\n",
+ "# Version: v295",
+ V295_COLUMNS,
+ "7\te4fivA1\tTrue\t1.1.1.3\t4fiv\tA\tA:4-116\tA:1-113\tbeta barrels"
+ + "\tcradle loop barrel\tRIFT-related\tacid protease\t\t\t\t1\tFalse"
+ + "\tFalse\tFalse\tFalse\tTrue\tTrue\tTrue\tLP1\tA:201"));
+ assertEquals(1, domains.size());
+ assertEquals("", domains.get(0).getFGroupName());
+ }
+ }
+
+ @Nested
+ class Robustness {
+ @Test
+ void unparseableLinesAreSkippedNotFatal() throws IOException {
+ List domains = parse(String.join("\n",
+ "# Version: v295",
+ V295_COLUMNS,
+ "not-a-number\tefoo\tTrue\t1.1.1.3\tfoo1\tA\tA:1-9\tA:1-9\ta\tb\tc\td\te"
+ + "\t\t\t1\tFalse\tFalse\tFalse\tFalse\tTrue\tTrue\tFalse\t\t",
+ "0\te2nmzA1\tTrue\t1.1.1.3\t2nmz\tA\tA:1-99\tA:1-99\tbeta barrels"
+ + "\tcradle loop barrel\tRIFT-related\tacid protease\tRVP\t\t\t1"
+ + "\tFalse\tFalse\tFalse\tFalse\tTrue\tTrue\tTrue\tROC,SO4\tA:601"));
+ assertEquals(1, domains.size(), "the good line is still read");
+ assertEquals("e2nmzA1", domains.get(0).getDomainId());
+ }
+
+ @Test
+ void shortLineIsSkipped() throws IOException {
+ assertTrue(parse(String.join("\n",
+ "# Version: v295",
+ V295_COLUMNS,
+ "0\te2nmzA1\tTrue")).isEmpty());
+ }
+
+ @Test
+ void emptyFileYieldsNoDomains() throws IOException {
+ assertTrue(parse("").isEmpty());
+ }
+ }
+}
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestHeaderOnly.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestHeaderOnly.java
index d3c9568240..f579752d4c 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestHeaderOnly.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestHeaderOnly.java
@@ -205,8 +205,7 @@ public boolean doSeqResHaveAtoms(Structure s) {
* @return true if has any Atom(s)
*/
public boolean hasAtoms(Group g) {
- if (g.getAtoms().size() > 0) return true;
- return false;
+ return !g.getAtoms().isEmpty();
}
/**
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestMMcifOrganismParsing.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestMMcifOrganismParsing.java
index 8d018f6c0a..2cbdbca679 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestMMcifOrganismParsing.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestMMcifOrganismParsing.java
@@ -37,7 +37,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
public class TestMMcifOrganismParsing {
@@ -90,7 +90,7 @@ private void checkPDB(String pdbId, String organismTaxId) throws IOException, St
Structure s = StructureIO.getStructure(pdbId);
assertNotNull(s.getEntityInfos());
- assertTrue(s.getEntityInfos().size() > 0);
+ assertFalse(s.getEntityInfos().isEmpty());
for ( EntityInfo c : s.getEntityInfos()) {
if(EntityType.POLYMER.equals(c.getType())) {
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestSiftsParsing.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestSiftsParsing.java
index 6a9f6ae93c..2b99d660d8 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestSiftsParsing.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/TestSiftsParsing.java
@@ -47,9 +47,9 @@ public void test4DIA() throws Exception {
for (SiftsEntity e : entities) {
//System.out.println(e.getEntityId() + " " +e.getType());
- Assert.assertTrue(e.getSegments().size() > 0);
+ Assert.assertFalse(e.getSegments().isEmpty());
for (SiftsSegment seg : e.getSegments()) {
- Assert.assertTrue(seg.getResidues().size() > 0);
+ Assert.assertFalse(seg.getResidues().isEmpty());
for (SiftsResidue res : seg.getResidues()) {
@@ -78,9 +78,9 @@ public void test4jn3() throws Exception {
for (SiftsEntity e : entities) {
//System.out.println(e.getEntityId() + " " +e.getType());
- Assert.assertTrue(e.getSegments().size() > 0);
+ Assert.assertFalse(e.getSegments().isEmpty());
for (SiftsSegment seg : e.getSegments()) {
- Assert.assertTrue(seg.getResidues().size() > 0);
+ Assert.assertFalse(seg.getResidues().isEmpty());
//System.out.println(seg.getResidues().size());
//System.out.println(" Segment: " + seg.getSegId() + " " + seg.getStart() + " " + seg.getEnd()) ;
@@ -125,7 +125,7 @@ public void test4DOU() throws Exception {
//assertTrue(seg1.getResidues().size() == 17);
for (SiftsSegment seg : e.getSegments()) {
- Assert.assertTrue(seg.getResidues().size() > 0);
+ Assert.assertFalse(seg.getResidues().isEmpty());
//System.out.println(" Segment: " + seg.getSegId() + " " + seg.getStart() + " " + seg.getEnd() + " res. size: " + seg.getResidues().size()) ;
@@ -175,7 +175,7 @@ public void test4O6W() throws Exception {
//System.out.println(" Segment: " + seg1.getSegId() + " " + seg1.getStart() + " " + seg1.getEnd() + " res. size: " + seg1.getResidues().size());
//assertTrue(seg1.getResidues().size() == 17);
- Assert.assertTrue(seg.getResidues().size() > 0);
+ Assert.assertFalse(seg.getResidues().isEmpty());
for (SiftsResidue res : seg.getResidues()) {
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileConsumerImplTest.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileConsumerImplTest.java
index a8925afa88..8fbd1060b0 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileConsumerImplTest.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileConsumerImplTest.java
@@ -147,7 +147,7 @@ public void testWaterOnlyChainCif() throws IOException {
Chain c = s2.getWaterChainByPDB("F");
assertNotNull("Got null when looking for water-only chain with author id F", c);
- assertTrue(c.getAtomGroups().size() > 0);
+ assertFalse(c.getAtomGroups().isEmpty());
// checking that compounds are linked
assertNotNull(c.getEntityInfo());
@@ -157,7 +157,7 @@ public void testWaterOnlyChainCif() throws IOException {
Chain cAsymId = s2.getWaterChain("E");
assertNotNull("Got null when looking for water-only chain with asym id E", cAsymId);
- assertTrue(cAsymId.getAtomGroups().size() > 0);
+ assertFalse(cAsymId.getAtomGroups().isEmpty());
assertSame(c, cAsymId);
}
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileSupplierImplTest.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileSupplierImplTest.java
index df227a8669..1d5f496ff5 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileSupplierImplTest.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/io/cif/CifFileSupplierImplTest.java
@@ -1,5 +1,6 @@
package org.biojava.nbio.structure.io.cif;
+import org.biojava.nbio.structure.PdbId;
import org.biojava.nbio.structure.Structure;
import org.biojava.nbio.structure.io.FileParsingParameters;
import org.biojava.nbio.structure.io.PDBFileParser;
@@ -41,4 +42,43 @@ public void shouldReadRawPdbOutputtingCifWithEntity() throws IOException {
}
}
+
+ /**
+ * The identifier must be written as a data item and not only as the name of the data block: consumers read it
+ * from _entry.id or from _struct.entry_id, so writing the block header alone loses it. See issue #1143.
+ */
+ @Test
+ public void shouldWriteEntryIdAndSurviveRoundTrip() throws IOException {
+ Structure s;
+ try (InputStream inStream = new GZIPInputStream(this.getClass().getResourceAsStream("/4hhb.cif.gz"))) {
+ s = CifStructureConverter.fromInputStream(inStream);
+ }
+ assertEquals(new PdbId("4HHB"), s.getPdbId());
+
+ String cifText = CifStructureConverter.toText(s);
+ assertTrue("_entry.id must be written", cifText.contains("_entry.id"));
+ assertTrue("_struct.entry_id must be written", cifText.contains("_struct.entry_id"));
+
+ Structure readStruct = CifStructureConverter.fromInputStream(
+ new ByteArrayInputStream(cifText.getBytes()));
+
+ assertEquals(s.getPdbId(), readStruct.getPdbId());
+ assertEquals(s.getPdbId(), readStruct.getPDBHeader().getPdbId());
+ }
+
+ /**
+ * Structures without an identifier must not gain empty entry categories.
+ */
+ @Test
+ public void shouldNotWriteEntryIdWhenPdbIdIsAbsent() throws IOException {
+ Structure s;
+ try (InputStream inStream = new GZIPInputStream(this.getClass().getResourceAsStream("/4hhb.cif.gz"))) {
+ s = CifStructureConverter.fromInputStream(inStream);
+ }
+ s.setPdbId(null);
+
+ String cifText = CifStructureConverter.toText(s);
+ assertFalse(cifText.contains("_entry.id"));
+ assertFalse(cifText.contains("_struct.entry_id"));
+ }
}
diff --git a/biojava-survival/pom.xml b/biojava-survival/pom.xml
index 4f3e00f56b..570a2dd99b 100644
--- a/biojava-survival/pom.xml
+++ b/biojava-survival/pom.xml
@@ -4,7 +4,7 @@
org.biojava
biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava-survival
diff --git a/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/CoxInfo.java b/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/CoxInfo.java
index c9f4f18056..eebdaf86ba 100644
--- a/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/CoxInfo.java
+++ b/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/CoxInfo.java
@@ -32,7 +32,7 @@
/**
* Holds the results of a cox analysis where calling dump(), toString() will give an output similar to R
- * @author Scooter Willis
+ * @author Scooter Willis
*/
public class CoxInfo {
@@ -505,7 +505,7 @@ public String toString(String beginLine, String del, String endLine) {
o = o + beginLine + endLine;
- if (baselineSurvivorFunction.size() > 0) {
+ if (!baselineSurvivorFunction.isEmpty()) {
o = o + beginLine + "Baseline Survivor Function (at predictor means)" + endLine;
for (Double time : baselineSurvivorFunction.keySet()) {
Double mean = baselineSurvivorFunction.get(time);
diff --git a/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/ResidualsCoxph.java b/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/ResidualsCoxph.java
index 42b34905cc..955a7c1f6d 100644
--- a/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/ResidualsCoxph.java
+++ b/biojava-survival/src/main/java/org/biojava/nbio/survival/cox/ResidualsCoxph.java
@@ -29,7 +29,7 @@
/**
*
- * @author Scooter Willis
+ * @author Scooter Willis
*/
public class ResidualsCoxph {
@@ -108,7 +108,7 @@ public static double[][] process(CoxInfo ci, Type type, boolean useWeighted, Arr
double[] weighted = ci.getWeighted();
rr = Matrix.scale(rr, weighted);
}
- if (cluster != null && cluster.size() > 0) {
+ if (cluster != null && !cluster.isEmpty()) {
rr = rowsum(rr, cluster);
}
diff --git a/biojava-survival/src/main/java/org/biojava/nbio/survival/data/WorkSheet.java b/biojava-survival/src/main/java/org/biojava/nbio/survival/data/WorkSheet.java
index 542085942e..17ed18419a 100644
--- a/biojava-survival/src/main/java/org/biojava/nbio/survival/data/WorkSheet.java
+++ b/biojava-survival/src/main/java/org/biojava/nbio/survival/data/WorkSheet.java
@@ -27,7 +27,7 @@
* Need to handle very large spreadsheets of expression data so keep memory
* footprint low
*
- * @author Scooter Willis
+ * @author Scooter Willis
*/
public class WorkSheet {
@@ -1391,7 +1391,7 @@ static public WorkSheet unionWorkSheetsRowJoin(WorkSheet w1, WorkSheet w2, boole
ArrayList joinedColumns = new ArrayList<>();
joinedColumns.addAll(w1DataColumns);
joinedColumns.addAll(w2DataColumns);
- if (!joinedColumns.contains("META_DATA") && (w1MetaDataColumns.size() > 0 || w2MetaDataColumns.size() > 0)) {
+ if (!joinedColumns.contains("META_DATA") && (!w1MetaDataColumns.isEmpty() || !w2MetaDataColumns.isEmpty())) {
joinedColumns.add("META_DATA");
}
for (String column : w1MetaDataColumns) {
diff --git a/biojava-survival/src/main/java/org/biojava/nbio/survival/kaplanmeier/figure/NumbersAtRiskPanel.java b/biojava-survival/src/main/java/org/biojava/nbio/survival/kaplanmeier/figure/NumbersAtRiskPanel.java
index c4578f1c8b..144942a809 100644
--- a/biojava-survival/src/main/java/org/biojava/nbio/survival/kaplanmeier/figure/NumbersAtRiskPanel.java
+++ b/biojava-survival/src/main/java/org/biojava/nbio/survival/kaplanmeier/figure/NumbersAtRiskPanel.java
@@ -76,7 +76,7 @@ private void paintTable(Graphics g) {
sfiHashMap = sfi.getStrataInfoHashMap();
}
- if(sfiHashMap.size() == 0)
+ if(sfiHashMap.isEmpty())
return;
//int height = this.getHeight();
diff --git a/biojava-ws/pom.xml b/biojava-ws/pom.xml
index 7a4fa63589..c3fe2a9513 100644
--- a/biojava-ws/pom.xml
+++ b/biojava-ws/pom.xml
@@ -3,7 +3,7 @@
biojava
org.biojava
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava-ws
biojava-ws
@@ -19,7 +19,7 @@
org.biojava
biojava-core
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
compile
diff --git a/biojava-ws/src/main/java/org/biojava/nbio/ws/hmmer/HmmerResult.java b/biojava-ws/src/main/java/org/biojava/nbio/ws/hmmer/HmmerResult.java
index 3304e78d4f..373b78cd0a 100644
--- a/biojava-ws/src/main/java/org/biojava/nbio/ws/hmmer/HmmerResult.java
+++ b/biojava-ws/src/main/java/org/biojava/nbio/ws/hmmer/HmmerResult.java
@@ -140,7 +140,7 @@ public int compareTo(HmmerResult o) {
return(me.getSqFrom().compareTo(other.getSqFrom()));
}
private boolean emptyDomains(HmmerResult o) {
- if ( o.getDomains() == null || o.getDomains().size() == 0)
+ if ( o.getDomains() == null || o.getDomains().isEmpty())
return true;
return false;
}
diff --git a/pom.xml b/pom.xml
index f17d3c2d67..79db94ee26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
org.biojava
biojava
pom
- 7.2.3-SNAPSHOT
+ 7.2.7-SNAPSHOT
biojava
BioJava is an open-source project dedicated to providing a Java framework for processing biological
data. It provides analytical and statistical routines, parsers for common file formats and allows the
@@ -41,7 +41,7 @@
512M
1.0.11
2.0.12
- 2.23.1
+ 2.25.5
5.10.1
ciftools-java
7.0.1
@@ -217,7 +217,8 @@
3.1.1
true
- clean install
+ -DskipTests
+ clean verify -DskipTests
true
@@ -325,12 +326,6 @@
3.1.3
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 3.11.2
-
-
org.apache.maven.plugins
maven-site-plugin
@@ -486,7 +481,7 @@
com.google.guava
guava
- 33.4.0-jre
+ 33.6.0-jre
@@ -580,17 +575,20 @@
release
-
-
+
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.13
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.8.0
true
- ossrh
- https://oss.sonatype.org/
- true
+
+ central
+
+
+ true
+
+ published