From f45e01876a1179983d96b9fde840614a0adf5915 Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Mon, 8 Jan 2024 12:04:02 -0600 Subject: [PATCH 1/8] Changed the URLs to the mirrors from http to https. Changed the name of the URL path to reflect a change in the v2 naming. --- .../nbio/structure/scop/ScopInstallation.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) 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 b7ee585279..019e084a5d 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 @@ -85,8 +85,8 @@ public class ScopInstallation implements LocalScopDatabase { public static final String comFileName = "dir.com.scop.txt_"; // Download locations - public static final String SCOP_DOWNLOAD = "http://scop.berkeley.edu/downloads/parse/"; - public static final String SCOP_DOWNLOAD_ALTERNATE = "http://scop.berkeley.edu/downloads/parse/"; + public static final String SCOP_DOWNLOAD = "https://scop.berkeley.edu/downloads/parse/"; + public static final String SCOP_DOWNLOAD_ALTERNATE = "https://scop.berkeley.edu/downloads/parse/"; //public static final String NEWLINE = System.getProperty("line.separator"); public static final String FILESPLIT = System.getProperty("file.separator"); @@ -913,10 +913,19 @@ private void initScopURLs() { // first, try default scop ScopMirror primary = new ScopMirror(); // If unreachable, try alternate Berkeley location - ScopMirror alt = new ScopMirror( - SCOP_DOWNLOAD_ALTERNATE, - "dir.cla.scop.%s.txt","dir.des.scop.%s.txt", - "dir.hie.scop.%s.txt","dir.com.scop.%s.txt"); + ScopMirror alt; + if (scopVersion.startsWith("2.")) { + alt = new ScopMirror( + SCOP_DOWNLOAD_ALTERNATE, + "dir.cla.scope.%s.txt","dir.des.scope.%s.txt", + "dir.hie.scope.%s.txt","dir.com.scope.%s.txt"); + } + else { + alt = new ScopMirror( + SCOP_DOWNLOAD_ALTERNATE, + "dir.cla.scop.%s.txt","dir.des.scop.%s.txt", + "dir.hie.scop.%s.txt","dir.com.scop.%s.txt"); + } mirrors.add(primary); mirrors.add(alt); } From 96c81269b33a2e2c2ac0b09c055a81ccfb5c5303 Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Sat, 13 Jan 2024 10:31:04 -0600 Subject: [PATCH 2/8] Before finding the InputStreamProvider --- .../nbio/core/sequence/io/FastaStreamer.java | 155 ++++++++++++++++++ .../biojava/nbio/core/util/MagicNumber.java | 39 +++++ .../core/sequence/io/FastaStreamerTest.java | 39 +++++ .../nbio/core/util/MagicNumberTest.java | 22 +++ .../org/biojava/nbio/core/util/example.gz | Bin 0 -> 54 bytes 5 files changed, 255 insertions(+) create mode 100644 biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java create mode 100644 biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java create mode 100644 biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java create mode 100644 biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java create mode 100644 biojava-core/src/test/resources/org/biojava/nbio/core/util/example.gz diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java new file mode 100644 index 0000000000..febf747a36 --- /dev/null +++ b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java @@ -0,0 +1,155 @@ +package org.biojava.nbio.core.sequence.io; + +import org.biojava.nbio.core.sequence.ProteinSequence; +import org.biojava.nbio.core.sequence.compound.AminoAcidCompound; +import org.biojava.nbio.core.sequence.compound.AminoAcidCompoundSet; +import org.biojava.nbio.core.sequence.io.template.SequenceCreatorInterface; +import org.biojava.nbio.core.sequence.io.template.SequenceHeaderParserInterface; +import org.biojava.nbio.core.util.MagicNumber; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import java.util.zip.GZIPInputStream; + +public class FastaStreamer { + + private final Path path; + private int batchSize = 1_000; + private SequenceHeaderParserInterface headerParser; + private SequenceCreatorInterface sequenceCreator; + private LinkedHashMap chunk = new LinkedHashMap<>(); + private Iterator> iterator = Collections.emptyIterator(); + private boolean closed = false; + + /** + * The constructor is private. Created via the from(...) static factory method + * + * @param path the path to the file containing the FASTA content (possibly GZipped) + */ + private FastaStreamer(final Path path) { + this.path = path; + } + + public static FastaStreamer from(final Path path) { + return new FastaStreamer(path); + } + + public static FastaStreamer from(File file) { + return from(file.toPath()); + } + + public FastaStreamer withHeaderParser(SequenceHeaderParserInterface headerParser) { + this.headerParser = headerParser; + return this; + } + + public FastaStreamer withSequenceCreator(SequenceCreatorInterface sequenceCreator) { + this.sequenceCreator = sequenceCreator; + return this; + } + + public FastaStreamer batchSize(int size) { + this.batchSize = size; + return this; + } + + /** + * Create a stream of protein sequences from the contents of the path + * @return the stream + * @throws IOException if there is an error opening the file + */ + public Stream stream() throws IOException { + InputStream rawInput = Files.newInputStream(getPath(), StandardOpenOption.READ); + InputStream input = MagicNumber.isGZIP(getPath()) ? new GZIPInputStream(rawInput) : rawInput; + FastaReader reader = new FastaReader<>(input, getHeaderParser(), getSequenceCreator()); + Spliterator source = new Spliterators.AbstractSpliterator<>(Integer.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL) { + @Override + public boolean tryAdvance(Consumer action) { + if (closed) { + return false; + } + ProteinSequence protein = next(reader); + if (null == protein) { + return false; + } + action.accept(protein); + return true; + } + + /** + * Fetch the next header/protein tuple from the cache. If the cache is empty, fetch another + * batch from the source file + * + * @param reader + * the input stream from which the FASTA content is read + * @return the protein sequence + */ + private ProteinSequence next(FastaReader reader) { + try { + if (!iterator.hasNext()) { + chunk = reader.process(getBatchSize()); + if (null == chunk) { + closed = true; + reader.close(); + return null; + } + iterator = chunk.entrySet().iterator(); + } + if (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + return createSequence(entry.getKey(), entry.getValue()); + } + closed = true; + reader.close(); + } catch (IOException exception) { + throw new RuntimeException(String.format("I/O error reading the FASTA file from '%s'", getPath())); + } + return null; + } + }; // Spliterator + return StreamSupport.stream(source, false); + } + + /** + * Create the sequence with the information from the header. This implementation return the sequence as-is, but + * this is an opportunity for the implementer to build specifc information into the user collection space + * of the sequence + * + * @param header the original header + * @param sequence the protein sequence + * @return the sequence + */ + protected ProteinSequence createSequence(String header, ProteinSequence sequence) { + return sequence; + } + + protected Path getPath() { + return path; + } + + protected int getBatchSize() { + return batchSize; + } + + protected SequenceHeaderParserInterface getHeaderParser() { + return Optional.ofNullable(headerParser).orElse(new GenericFastaHeaderParser<>()); + } + + public SequenceCreatorInterface getSequenceCreator() { + return Optional.ofNullable(sequenceCreator).orElse(new ProteinSequenceCreator(AminoAcidCompoundSet.getAminoAcidCompoundSet())); + } +} diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java b/biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java new file mode 100644 index 0000000000..9c6765b354 --- /dev/null +++ b/biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java @@ -0,0 +1,39 @@ +package org.biojava.nbio.core.util; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +/** + * The 'magic number' is a sequence of bytes that the beginning of a file that can be used to determine the + * file type + * + * @since 7.0.3 + * @author Gary Murphy + */ +public class MagicNumber { + + /** + * The magic number of a gzip file is 0x1F8B. (ref: https://en.wikipedia.org/wiki/Gzip#:~:text=%22gzip%22%20is%20often%20also%20used,and%20the%20operating%20system%20ID.) + * @param path the path to the file + * @return true if the file has the gzip magic number + * @throws IOException if there is an error reading the start of the file + */ + public static boolean isGZIP(Path path) throws IOException { + try ( + InputStream input = Files.newInputStream(path, StandardOpenOption.READ) + ) { + byte[] magic = new byte[2]; + int count = input.read(magic); + if (count != 2) { + return false; + } + int id = (int)magic[0] & 0x00ff; + id <<= 8; + id += (int)magic[1] & 0x00ff; + return (id == 0x1f8b); + } + } +} diff --git a/biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java b/biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java new file mode 100644 index 0000000000..f17ba93de1 --- /dev/null +++ b/biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java @@ -0,0 +1,39 @@ +package org.biojava.nbio.core.sequence.io; + +import org.biojava.nbio.core.sequence.ProteinSequence; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Test the functionality of the {@link FastaStreamer} code + */ +public class FastaStreamerTest { + + @Test + public void stream() throws IOException { + String file = this.getClass().getResource("PF00104_small.fasta.gz").getFile(); + Path path = Paths.get(file); + List sequences; + + sequences = FastaStreamer.from(path).stream().collect(Collectors.toList()); + Assert.assertEquals("Count", 283, sequences.size()); + + ProteinSequence sequence; + sequence = sequences.get(0); + Assert.assertEquals("A2D504_ATEGE/1-46", sequence.getOriginalHeader()); + sequence = sequences.get(sequences.size()-1); + Assert.assertEquals("Q98SJ1_CHICK/15-61", sequence.getOriginalHeader()); + + sequences = FastaStreamer.from(path) + .batchSize(2) // Ensure there isn't an edge condition loading the next buffer + .stream() + .collect(Collectors.toList()); + Assert.assertEquals("Count", 283, sequences.size()); + } +} diff --git a/biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java b/biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java new file mode 100644 index 0000000000..eed6ae7611 --- /dev/null +++ b/biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java @@ -0,0 +1,22 @@ +package org.biojava.nbio.core.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class MagicNumberTest { + + @Test + public void gzip() throws IOException { + String file = this.getClass().getResource("example.gz").getFile(); + Path path = Paths.get(file); + Assert.assertTrue("GZIP file", MagicNumber.isGZIP(path)); + + file = this.getClass().getResource("build.xml").getFile(); + path = Paths.get(file); + Assert.assertFalse("Not a GZIP file", MagicNumber.isGZIP(path)); + } +} diff --git a/biojava-core/src/test/resources/org/biojava/nbio/core/util/example.gz b/biojava-core/src/test/resources/org/biojava/nbio/core/util/example.gz new file mode 100644 index 0000000000000000000000000000000000000000..0864558ee236c78ac01f1334a951d44fae728c9a GIT binary patch literal 54 zcmb2|=HQsLV^Jytb81CmZb42e1GlHH-pMoG;RXGlcmh87`keO(`=EW+*Gu;i6N7*m K+gm9H1_l5iOA`wK literal 0 HcmV?d00001 From fd3be7e9f1e3c177fbef509bfd3e795bf8969de8 Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Sat, 13 Jan 2024 10:50:10 -0600 Subject: [PATCH 3/8] Removed the MagicNumber class after finding InputStreamProvider --- .../nbio/core/sequence/io/FastaStreamer.java | 9 ++-- .../biojava/nbio/core/util/MagicNumber.java | 39 ------------------ .../nbio/core/util/MagicNumberTest.java | 22 ---------- .../core/sequence/io/PF00104_small.fasta.gz | Bin 0 -> 31036 bytes 4 files changed, 3 insertions(+), 67 deletions(-) delete mode 100644 biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java delete mode 100644 biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java create mode 100644 biojava-core/src/test/resources/org/biojava/nbio/core/sequence/io/PF00104_small.fasta.gz diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java index febf747a36..f0c15c7a9e 100644 --- a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java +++ b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java @@ -5,14 +5,12 @@ import org.biojava.nbio.core.sequence.compound.AminoAcidCompoundSet; import org.biojava.nbio.core.sequence.io.template.SequenceCreatorInterface; import org.biojava.nbio.core.sequence.io.template.SequenceHeaderParserInterface; -import org.biojava.nbio.core.util.MagicNumber; +import org.biojava.nbio.core.util.InputStreamProvider; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -23,7 +21,6 @@ import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import java.util.zip.GZIPInputStream; public class FastaStreamer { @@ -73,8 +70,8 @@ public FastaStreamer batchSize(int size) { * @throws IOException if there is an error opening the file */ public Stream stream() throws IOException { - InputStream rawInput = Files.newInputStream(getPath(), StandardOpenOption.READ); - InputStream input = MagicNumber.isGZIP(getPath()) ? new GZIPInputStream(rawInput) : rawInput; + InputStreamProvider provider = new InputStreamProvider(); + InputStream input = provider.getInputStream(getPath().toFile()); FastaReader reader = new FastaReader<>(input, getHeaderParser(), getSequenceCreator()); Spliterator source = new Spliterators.AbstractSpliterator<>(Integer.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL) { @Override diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java b/biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java deleted file mode 100644 index 9c6765b354..0000000000 --- a/biojava-core/src/main/java/org/biojava/nbio/core/util/MagicNumber.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.biojava.nbio.core.util; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; - -/** - * The 'magic number' is a sequence of bytes that the beginning of a file that can be used to determine the - * file type - * - * @since 7.0.3 - * @author Gary Murphy - */ -public class MagicNumber { - - /** - * The magic number of a gzip file is 0x1F8B. (ref: https://en.wikipedia.org/wiki/Gzip#:~:text=%22gzip%22%20is%20often%20also%20used,and%20the%20operating%20system%20ID.) - * @param path the path to the file - * @return true if the file has the gzip magic number - * @throws IOException if there is an error reading the start of the file - */ - public static boolean isGZIP(Path path) throws IOException { - try ( - InputStream input = Files.newInputStream(path, StandardOpenOption.READ) - ) { - byte[] magic = new byte[2]; - int count = input.read(magic); - if (count != 2) { - return false; - } - int id = (int)magic[0] & 0x00ff; - id <<= 8; - id += (int)magic[1] & 0x00ff; - return (id == 0x1f8b); - } - } -} diff --git a/biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java b/biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java deleted file mode 100644 index eed6ae7611..0000000000 --- a/biojava-core/src/test/java/org/biojava/nbio/core/util/MagicNumberTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.biojava.nbio.core.util; - -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class MagicNumberTest { - - @Test - public void gzip() throws IOException { - String file = this.getClass().getResource("example.gz").getFile(); - Path path = Paths.get(file); - Assert.assertTrue("GZIP file", MagicNumber.isGZIP(path)); - - file = this.getClass().getResource("build.xml").getFile(); - path = Paths.get(file); - Assert.assertFalse("Not a GZIP file", MagicNumber.isGZIP(path)); - } -} diff --git a/biojava-core/src/test/resources/org/biojava/nbio/core/sequence/io/PF00104_small.fasta.gz b/biojava-core/src/test/resources/org/biojava/nbio/core/sequence/io/PF00104_small.fasta.gz new file mode 100644 index 0000000000000000000000000000000000000000..d4a340c73d7c9684bb281e06ca52bc6228403414 GIT binary patch literal 31036 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 HRiFX@f9c@5 literal 0 HcmV?d00001 From ef665e26ce560ac2c14af46535e250f61336d84b Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Sun, 14 Jan 2024 07:37:34 -0600 Subject: [PATCH 4/8] Added comments --- .../org/biojava/nbio/core/sequence/io/FastaStreamer.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java index f0c15c7a9e..0311bddc2b 100644 --- a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java +++ b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java @@ -22,6 +22,13 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +/** + * Read from a FASTA file (or gzipped FASTA file) and create a Java stream of {@link ProteinSequence} objects + * for use in a functional programming paradigm. + * + * @author Gary Murphy + * @since 7.0.3 + */ public class FastaStreamer { private final Path path; @@ -123,7 +130,7 @@ private ProteinSequence next(FastaReader rea /** * Create the sequence with the information from the header. This implementation return the sequence as-is, but - * this is an opportunity for the implementer to build specifc information into the user collection space + * this is an opportunity for the implementer to build specific information into the user collection space * of the sequence * * @param header the original header From 26b21d7c4c47f6020fa0a6950778b1046e301336 Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Sun, 14 Jan 2024 09:40:58 -0600 Subject: [PATCH 5/8] Added access to the iterable --- .../nbio/core/sequence/io/FastaStreamer.java | 26 ++++++++++++++++--- .../core/sequence/io/FastaStreamerTest.java | 11 ++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java index 0311bddc2b..803287cb90 100644 --- a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java +++ b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java @@ -71,14 +71,34 @@ public FastaStreamer batchSize(int size) { return this; } + /** + * Enable iteration through the proteins in the file using syntax such as: + *
+	 *     for(ProteinSequence sequence : FastaStreamer.from(path).each()) {
+	 *         .
+	 *         .
+	 *         .
+	 *     }
+	 * 
+ * + * @return an iterable suitable for an iteration loop + */ + public Iterable each() { + return () -> stream().iterator(); + } + /** * Create a stream of protein sequences from the contents of the path * @return the stream - * @throws IOException if there is an error opening the file */ - public Stream stream() throws IOException { + public Stream stream() { InputStreamProvider provider = new InputStreamProvider(); - InputStream input = provider.getInputStream(getPath().toFile()); + InputStream input; + try { + input = provider.getInputStream(getPath().toFile()); + } catch (IOException exception) { + throw new RuntimeException(exception); + } FastaReader reader = new FastaReader<>(input, getHeaderParser(), getSequenceCreator()); Spliterator source = new Spliterators.AbstractSpliterator<>(Integer.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL) { @Override diff --git a/biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java b/biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java index f17ba93de1..614c6f06c8 100644 --- a/biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java +++ b/biojava-core/src/test/java/org/biojava/nbio/core/sequence/io/FastaStreamerTest.java @@ -36,4 +36,15 @@ public void stream() throws IOException { .collect(Collectors.toList()); Assert.assertEquals("Count", 283, sequences.size()); } + + @Test + public void iterate() { + String file = this.getClass().getResource("PF00104_small.fasta.gz").getFile(); + Path path = Paths.get(file); + int count = 0; + for (ProteinSequence sequence : FastaStreamer.from(path).each()) { + count++; + } + Assert.assertEquals("Count", 283, count); + } } From d081b125792b8d07c4eb37f3dfc8d4575aef2335 Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Fri, 26 Jan 2024 07:14:06 -0600 Subject: [PATCH 6/8] Changed the version to 7.1.0-SNAPSHOT. Made changes per review. --- biojava-aa-prop/pom.xml | 6 +++--- biojava-alignment/pom.xml | 4 ++-- biojava-core/pom.xml | 2 +- .../biojava/nbio/core/sequence/io/FastaStreamer.java | 10 +++++----- biojava-genome/pom.xml | 6 +++--- biojava-integrationtest/pom.xml | 4 ++-- biojava-modfinder/pom.xml | 4 ++-- biojava-ontology/pom.xml | 2 +- biojava-protein-comparison-tool/pom.xml | 10 +++++----- biojava-protein-disorder/pom.xml | 4 ++-- biojava-structure-gui/pom.xml | 6 +++--- biojava-structure/pom.xml | 6 +++--- biojava-survival/pom.xml | 2 +- biojava-ws/pom.xml | 4 ++-- pom.xml | 2 +- 15 files changed, 36 insertions(+), 36 deletions(-) diff --git a/biojava-aa-prop/pom.xml b/biojava-aa-prop/pom.xml index 908f2d28c2..84fdbf3a7e 100644 --- a/biojava-aa-prop/pom.xml +++ b/biojava-aa-prop/pom.xml @@ -2,7 +2,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT 4.0.0 biojava-aa-prop @@ -70,12 +70,12 @@ org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT org.biojava biojava-structure - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT diff --git a/biojava-alignment/pom.xml b/biojava-alignment/pom.xml index 002fbb6b0b..6ce294e7e3 100644 --- a/biojava-alignment/pom.xml +++ b/biojava-alignment/pom.xml @@ -4,7 +4,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-alignment biojava-alignment @@ -47,7 +47,7 @@ org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile diff --git a/biojava-core/pom.xml b/biojava-core/pom.xml index fb44bbadd3..b0e4fa55c0 100644 --- a/biojava-core/pom.xml +++ b/biojava-core/pom.xml @@ -3,7 +3,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT 4.0.0 biojava-core diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java index 803287cb90..601c1226ec 100644 --- a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java +++ b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java @@ -10,6 +10,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.Collections; import java.util.Iterator; @@ -27,7 +28,7 @@ * for use in a functional programming paradigm. * * @author Gary Murphy - * @since 7.0.3 + * @since 7.1.0 */ public class FastaStreamer { @@ -135,12 +136,12 @@ private ProteinSequence next(FastaReader rea } if (iterator.hasNext()) { Map.Entry entry = iterator.next(); - return createSequence(entry.getKey(), entry.getValue()); + return createSequence(entry.getValue()); } closed = true; reader.close(); } catch (IOException exception) { - throw new RuntimeException(String.format("I/O error reading the FASTA file from '%s'", getPath())); + throw new UncheckedIOException(String.format("I/O error reading the FASTA file from '%s'", getPath()), exception); } return null; } @@ -153,11 +154,10 @@ private ProteinSequence next(FastaReader rea * this is an opportunity for the implementer to build specific information into the user collection space * of the sequence * - * @param header the original header * @param sequence the protein sequence * @return the sequence */ - protected ProteinSequence createSequence(String header, ProteinSequence sequence) { + protected ProteinSequence createSequence(ProteinSequence sequence) { return sequence; } diff --git a/biojava-genome/pom.xml b/biojava-genome/pom.xml index 2b213d7de5..f0d8e13c2f 100644 --- a/biojava-genome/pom.xml +++ b/biojava-genome/pom.xml @@ -3,7 +3,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT 4.0.0 biojava-genome @@ -71,13 +71,13 @@ org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile org.biojava biojava-alignment - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile diff --git a/biojava-integrationtest/pom.xml b/biojava-integrationtest/pom.xml index d02011571c..cee137b4c1 100644 --- a/biojava-integrationtest/pom.xml +++ b/biojava-integrationtest/pom.xml @@ -4,7 +4,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-integrationtest jar @@ -40,7 +40,7 @@ org.biojava biojava-structure - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT diff --git a/biojava-modfinder/pom.xml b/biojava-modfinder/pom.xml index ff8771babc..a7e07100c4 100644 --- a/biojava-modfinder/pom.xml +++ b/biojava-modfinder/pom.xml @@ -4,7 +4,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-modfinder biojava-modfinder @@ -31,7 +31,7 @@ org.biojava biojava-structure - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT jar compile diff --git a/biojava-ontology/pom.xml b/biojava-ontology/pom.xml index 1fb8967489..e4b3c3cdf2 100644 --- a/biojava-ontology/pom.xml +++ b/biojava-ontology/pom.xml @@ -4,7 +4,7 @@ org.biojava biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-ontology diff --git a/biojava-protein-comparison-tool/pom.xml b/biojava-protein-comparison-tool/pom.xml index 36d3ec3b2b..dd4b460858 100644 --- a/biojava-protein-comparison-tool/pom.xml +++ b/biojava-protein-comparison-tool/pom.xml @@ -4,7 +4,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-protein-comparison-tool @@ -36,23 +36,23 @@ org.biojava biojava-alignment - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT org.biojava biojava-structure - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT org.biojava biojava-structure-gui - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT net.sourceforge.jmol diff --git a/biojava-protein-disorder/pom.xml b/biojava-protein-disorder/pom.xml index c86173990f..67548aca4a 100644 --- a/biojava-protein-disorder/pom.xml +++ b/biojava-protein-disorder/pom.xml @@ -3,7 +3,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-protein-disorder jar @@ -63,7 +63,7 @@ org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT diff --git a/biojava-structure-gui/pom.xml b/biojava-structure-gui/pom.xml index 8b3522352b..3cc7cbe005 100644 --- a/biojava-structure-gui/pom.xml +++ b/biojava-structure-gui/pom.xml @@ -3,7 +3,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT 4.0.0 biojava-structure-gui @@ -27,13 +27,13 @@ org.biojava biojava-structure - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile diff --git a/biojava-structure/pom.xml b/biojava-structure/pom.xml index bd779d4ba0..8afe66b114 100644 --- a/biojava-structure/pom.xml +++ b/biojava-structure/pom.xml @@ -4,7 +4,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-structure biojava-structure @@ -44,13 +44,13 @@ org.biojava biojava-alignment - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile diff --git a/biojava-survival/pom.xml b/biojava-survival/pom.xml index 61bcb2369f..c9c0c60a55 100644 --- a/biojava-survival/pom.xml +++ b/biojava-survival/pom.xml @@ -4,7 +4,7 @@ org.biojava biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-survival diff --git a/biojava-ws/pom.xml b/biojava-ws/pom.xml index 86cf1004f9..258814c27d 100644 --- a/biojava-ws/pom.xml +++ b/biojava-ws/pom.xml @@ -3,7 +3,7 @@ biojava org.biojava - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT biojava-ws biojava-ws @@ -19,7 +19,7 @@ org.biojava biojava-core - 7.0.3-SNAPSHOT + 7.1.0-SNAPSHOT compile diff --git a/pom.xml b/pom.xml index 1b079ca711..efcddb8feb 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.biojava biojava pom - 7.0.3-SNAPSHOT + 7.1.0-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 From 2d1f32ea7a04ca92ca6b26a2e1381d0d44e61a01 Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Fri, 26 Jan 2024 07:16:51 -0600 Subject: [PATCH 7/8] Missed a RuntimeException. Changed to UncheckedIOException --- .../java/org/biojava/nbio/core/sequence/io/FastaStreamer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java index 601c1226ec..c9184dfd13 100644 --- a/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java +++ b/biojava-core/src/main/java/org/biojava/nbio/core/sequence/io/FastaStreamer.java @@ -98,7 +98,7 @@ public Stream stream() { try { input = provider.getInputStream(getPath().toFile()); } catch (IOException exception) { - throw new RuntimeException(exception); + throw new UncheckedIOException(exception); } FastaReader reader = new FastaReader<>(input, getHeaderParser(), getSequenceCreator()); Spliterator source = new Spliterators.AbstractSpliterator<>(Integer.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL) { From 84998b92ab0ba0b57a378d47d93e1451d5827d2e Mon Sep 17 00:00:00 2001 From: Gary Murphy Date: Tue, 30 Jan 2024 07:19:52 -0600 Subject: [PATCH 8/8] Added an entry to the changelog for FastaStreamer --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ae621112..c615c97558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ BioJava Changelog ----------------- +BioJava 7.1.0 - future release +============================== +### Added +* Class `FastaStreamer` to read FASTA-formatted files using Java streams + + BioJava 7.0.2 ============================== ### Added