From 70a5f29128290f0541fae0f14927de9b2f6f6fad Mon Sep 17 00:00:00 2001 From: jweiser Date: Wed, 3 Jun 2026 00:42:34 -0400 Subject: [PATCH] adds footers to report files --- .../java/org/reactome/DuplicateChecker.java | 2 + src/main/java/org/reactome/Main.java | 4 + .../org/reactome/database/DBInteractor.java | 7 ++ .../reactome/reports/AbstractReporter.java | 74 +++++++++++++++++++ .../DuplicateReferenceMoleculeReporter.java | 8 +- .../reports/FailedChEBILookupReporter.java | 9 ++- ...MoleculeChEBIIdentifierChangeReporter.java | 16 +++- ...eferenceMoleculeFormulaChangeReporter.java | 9 ++- .../ReferenceMoleculeNameChangeReporter.java | 9 ++- .../java/org/reactome/reports/Reportable.java | 43 ----------- .../SimpleEntityNameChangeReporter.java | 9 ++- ...{ReportableTest.java => ReporterTest.java} | 51 ++++++++----- 12 files changed, 171 insertions(+), 70 deletions(-) create mode 100644 src/main/java/org/reactome/reports/AbstractReporter.java delete mode 100644 src/main/java/org/reactome/reports/Reportable.java rename src/test/java/org/reactome/reports/{ReportableTest.java => ReporterTest.java} (81%) diff --git a/src/main/java/org/reactome/DuplicateChecker.java b/src/main/java/org/reactome/DuplicateChecker.java index ee2de42..cbb9e08 100644 --- a/src/main/java/org/reactome/DuplicateChecker.java +++ b/src/main/java/org/reactome/DuplicateChecker.java @@ -41,6 +41,8 @@ public void findAndLogDuplicates() throws Exception { referenceMolecule.getDisplayName() ); } + + this.duplicateReferenceMoleculeReporter.writeFooterIfInitialized(); } } diff --git a/src/main/java/org/reactome/Main.java b/src/main/java/org/reactome/Main.java index 3cd1913..d22937d 100644 --- a/src/main/java/org/reactome/Main.java +++ b/src/main/java/org/reactome/Main.java @@ -64,6 +64,10 @@ private static void updateReferenceMolecules(List referenceMolecules processedCount += referenceMoleculeBatch.size(); logger.info("Finished processing " + processedCount + " reference molecules"); } + + failedChEBILookupReporter.writeFooterIfInitialized(); + referenceMoleculeChEBIIdentifierChangeReporter.writeFooterIfInitialized(); + dbInteractor.closeReports(); } private static void updateReferenceMoleculeBatch(List referenceMoleculeBatch) throws Exception { diff --git a/src/main/java/org/reactome/database/DBInteractor.java b/src/main/java/org/reactome/database/DBInteractor.java index 9dfe17c..f2d56a3 100644 --- a/src/main/java/org/reactome/database/DBInteractor.java +++ b/src/main/java/org/reactome/database/DBInteractor.java @@ -12,6 +12,7 @@ import org.reactome.reports.ReferenceMoleculeNameChangeReporter; import org.reactome.reports.SimpleEntityNameChangeReporter; +import java.io.IOException; import java.sql.SQLException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -177,6 +178,12 @@ public boolean updateModifiedInstanceEdits(GKInstance instance) throws Exception return true; } + public void closeReports() throws IOException { + this.referenceMoleculeNameChangeReporter.writeFooterIfInitialized(); + this.referenceMoleculeFormulaChangeReporter.writeFooterIfInitialized(); + this.simpleEntityNameChangeReporter.writeFooterIfInitialized(); + } + GKInstance getInstanceEdit() throws Exception { if (instanceEdit == null) { instanceEdit = new GKInstance(getSchema().getClassByName(ReactomeJavaConstants.InstanceEdit)); diff --git a/src/main/java/org/reactome/reports/AbstractReporter.java b/src/main/java/org/reactome/reports/AbstractReporter.java new file mode 100644 index 0000000..9d1ad5c --- /dev/null +++ b/src/main/java/org/reactome/reports/AbstractReporter.java @@ -0,0 +1,74 @@ +package org.reactome.reports; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public abstract class AbstractReporter { + + private boolean initialized; + + public abstract String getHeader(); + + public abstract String getFooter(); + + public abstract Path getFilePath(); + + public Path getReportDirectory() { + return Paths.get("reports"); + } + + public void report(String ...values) throws IOException { + initializeIfNeeded(); + + String reportLine = String.join("\t", values).concat(System.lineSeparator()); + Files.write( + getFilePath(), + reportLine.getBytes(), + StandardOpenOption.APPEND + ); + } + + public void writeFooterIfInitialized() throws IOException { + if (!initialized) { + return; + } + + String footerWithPrependedNewLines = "" + .concat(System.lineSeparator()) + .concat(System.lineSeparator()) + .concat(getFooter()); + + Files.write( + getFilePath(), + footerWithPrependedNewLines.getBytes(), + StandardOpenOption.APPEND + ); + } + + private void writeHeader() throws IOException { + Files.write( + getFilePath(), + getHeader().concat(System.lineSeparator()).getBytes(), + StandardOpenOption.CREATE, StandardOpenOption.APPEND + ); + } + + private void initializeIfNeeded() throws IOException { + if (initialized) { + return; + } + + if (Files.notExists(getReportDirectory())) { + Files.createDirectories(getReportDirectory()); + } + + if (Files.notExists(getFilePath())) { + writeHeader(); + } + + initialized = true; + } +} diff --git a/src/main/java/org/reactome/reports/DuplicateReferenceMoleculeReporter.java b/src/main/java/org/reactome/reports/DuplicateReferenceMoleculeReporter.java index 3da2d65..1d56465 100644 --- a/src/main/java/org/reactome/reports/DuplicateReferenceMoleculeReporter.java +++ b/src/main/java/org/reactome/reports/DuplicateReferenceMoleculeReporter.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class DuplicateReferenceMoleculeReporter implements Reportable { +public class DuplicateReferenceMoleculeReporter extends AbstractReporter { @Override public String getHeader() { @@ -15,6 +15,12 @@ public String getHeader() { ); } + @Override + public String getFooter() { + return "This report specifies Reference Molecule instances with duplicated ChEBI identifiers. " + + "Where possible, these should be merged and/or all but one removed from the curator database."; + } + @Override public Path getFilePath() { return getReportDirectory().resolve(Paths.get("duplicates.tsv")); diff --git a/src/main/java/org/reactome/reports/FailedChEBILookupReporter.java b/src/main/java/org/reactome/reports/FailedChEBILookupReporter.java index af75c06..33a75d2 100644 --- a/src/main/java/org/reactome/reports/FailedChEBILookupReporter.java +++ b/src/main/java/org/reactome/reports/FailedChEBILookupReporter.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class FailedChEBILookupReporter implements Reportable { +public class FailedChEBILookupReporter extends AbstractReporter { @Override public String getHeader() { @@ -14,6 +14,13 @@ public String getHeader() { ); } + @Override + public String getFooter() { + return "This report specifies Reference Molecule instances whose ChEBI identifier couldn't be retrieved " + + "from the external ChEBI API. This may have been due to an intermittent service interruption or an " + + "invalid ChEBI identifier. The identifiers reported should be checked manually in ChEBI to verify them."; + } + @Override public Path getFilePath() { return getReportDirectory().resolve(Paths.get("failed-chebi-lookups.tsv")); diff --git a/src/main/java/org/reactome/reports/ReferenceMoleculeChEBIIdentifierChangeReporter.java b/src/main/java/org/reactome/reports/ReferenceMoleculeChEBIIdentifierChangeReporter.java index f2587cd..f2954e1 100644 --- a/src/main/java/org/reactome/reports/ReferenceMoleculeChEBIIdentifierChangeReporter.java +++ b/src/main/java/org/reactome/reports/ReferenceMoleculeChEBIIdentifierChangeReporter.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class ReferenceMoleculeChEBIIdentifierChangeReporter implements Reportable { +public class ReferenceMoleculeChEBIIdentifierChangeReporter extends AbstractReporter { @Override public String getHeader() { @@ -13,12 +13,20 @@ public String getHeader() { "Reference Molecule", "Deprecated Identifier", "Replacement Identifier", - "Affected referenceEntity DB_IDs", - "DB_ID of Molecule with Replacement Identifier", - "DB_IDs of referenceEntities of Molecule with Replacement Identifier" + "Affected Simple Entity DB_IDs", + "DB_ID of Reference Molecule with Replacement Identifier", + "DB_IDs of Simple Entities of Reference Molecule with Replacement Identifier" ); } + @Override + public String getFooter() { + return "This reports specifies Reference Molecule instances whose ChEBI identifier has changed. " + + "For any instances reported, it is necessary to move the SimpleEntity referrers to the new " + + "Reference Molecule with the replacement identifier and remove the old Reference Molecule with " + + "deprecated identifier"; + } + @Override public Path getFilePath() { return getReportDirectory().resolve(Paths.get("reference-molecule-chebi-identifier-changes.tsv")); diff --git a/src/main/java/org/reactome/reports/ReferenceMoleculeFormulaChangeReporter.java b/src/main/java/org/reactome/reports/ReferenceMoleculeFormulaChangeReporter.java index be6f243..fb08892 100644 --- a/src/main/java/org/reactome/reports/ReferenceMoleculeFormulaChangeReporter.java +++ b/src/main/java/org/reactome/reports/ReferenceMoleculeFormulaChangeReporter.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class ReferenceMoleculeFormulaChangeReporter implements Reportable { +public class ReferenceMoleculeFormulaChangeReporter extends AbstractReporter { @Override public String getHeader() { return String.join("\t", @@ -15,6 +15,13 @@ public String getHeader() { ); } + @Override + public String getFooter() { + return "This report specifies when a Reference Molecule instance has a new chemical formula. " + + "This report is only informational as the changes have already been applied in the database - " + + " nothing need be done."; + } + @Override public Path getFilePath() { return getReportDirectory().resolve(Paths.get("reference-molecule-formula-changes.tsv")); diff --git a/src/main/java/org/reactome/reports/ReferenceMoleculeNameChangeReporter.java b/src/main/java/org/reactome/reports/ReferenceMoleculeNameChangeReporter.java index ce0e404..5d182f8 100644 --- a/src/main/java/org/reactome/reports/ReferenceMoleculeNameChangeReporter.java +++ b/src/main/java/org/reactome/reports/ReferenceMoleculeNameChangeReporter.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class ReferenceMoleculeNameChangeReporter implements Reportable { +public class ReferenceMoleculeNameChangeReporter extends AbstractReporter { @Override public String getHeader() { return String.join("\t", @@ -15,6 +15,13 @@ public String getHeader() { ); } + @Override + public String getFooter() { + return "This report specifies when a Reference Molecule instance has a new name. " + + "This report is only informational as the changes have already been applied in the database - " + + " nothing need be done."; + } + @Override public Path getFilePath() { return getReportDirectory().resolve(Paths.get("reference-molecule-name-changes.tsv")); diff --git a/src/main/java/org/reactome/reports/Reportable.java b/src/main/java/org/reactome/reports/Reportable.java deleted file mode 100644 index efe3f47..0000000 --- a/src/main/java/org/reactome/reports/Reportable.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.reactome.reports; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; - -public interface Reportable { - - String getHeader(); - - Path getFilePath(); - - default Path getReportDirectory() { - return Paths.get("reports"); - } - - default void report(String ...values) throws IOException { - if (Files.notExists(getReportDirectory())) { - Files.createDirectories(getReportDirectory()); - } - - if (Files.notExists(getFilePath())) { - writeHeader(); - } - - String reportLine = String.join("\t", values).concat(System.lineSeparator()); - Files.write( - getFilePath(), - reportLine.getBytes(), - StandardOpenOption.APPEND - ); - } - - default void writeHeader() throws IOException { - Files.write( - getFilePath(), - getHeader().concat(System.lineSeparator()).getBytes(), - StandardOpenOption.CREATE, StandardOpenOption.APPEND - ); - } -} diff --git a/src/main/java/org/reactome/reports/SimpleEntityNameChangeReporter.java b/src/main/java/org/reactome/reports/SimpleEntityNameChangeReporter.java index bb723fb..8672c02 100644 --- a/src/main/java/org/reactome/reports/SimpleEntityNameChangeReporter.java +++ b/src/main/java/org/reactome/reports/SimpleEntityNameChangeReporter.java @@ -3,7 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -public class SimpleEntityNameChangeReporter implements Reportable { +public class SimpleEntityNameChangeReporter extends AbstractReporter { @Override public String getHeader() { @@ -18,6 +18,13 @@ public String getHeader() { ); } + @Override + public String getFooter() { + return "This report specifies Simple Entities with updated names. If the update is applied automatically " + + "(true for the last column), nothing need be done. If the update is not applied automatically, it is " + + "a suggestion for curators to apply what is given in the 'Update Simple Entity Names' column."; + } + @Override public Path getFilePath() { return getReportDirectory().resolve(Paths.get("simple-entity-name-changes.tsv")); diff --git a/src/test/java/org/reactome/reports/ReportableTest.java b/src/test/java/org/reactome/reports/ReporterTest.java similarity index 81% rename from src/test/java/org/reactome/reports/ReportableTest.java rename to src/test/java/org/reactome/reports/ReporterTest.java index 48bdd58..7bc0812 100644 --- a/src/test/java/org/reactome/reports/ReportableTest.java +++ b/src/test/java/org/reactome/reports/ReporterTest.java @@ -11,15 +11,14 @@ import static org.junit.jupiter.api.Assertions.*; -class ReportableTest { +class ReporterTest { @TempDir Path tempDir; private TestReporter reporter; - // Test implementation of Reportable interface - private static class TestReporter implements Reportable { + private static class TestReporter extends AbstractReporter { private final Path reportDirectory; private final String fileName; @@ -33,6 +32,11 @@ public String getHeader() { return "Column1\tColumn2\tColumn3"; } + @Override + public String getFooter() { + return "Example Footer"; + } + @Override public Path getFilePath() { return reportDirectory.resolve(fileName); @@ -55,7 +59,9 @@ void testReportCreatesDirectoryIfNotExists() throws IOException { TestReporter customReporter = new TestReporter(customDir, "test.tsv"); assertFalse(Files.exists(customDir)); + customReporter.report("value1", "value2", "value3"); + assertTrue(Files.exists(customDir)); } @@ -64,8 +70,10 @@ void testReportWritesHeaderOnFirstWrite() throws IOException { reporter.report("value1", "value2", "value3"); List lines = Files.readAllLines(reporter.getFilePath()); + assertEquals(2, lines.size()); assertEquals(reporter.getHeader(), lines.get(0)); + assertEquals("value1\tvalue2\tvalue3", lines.get(1)); } @Test @@ -74,33 +82,21 @@ void testReportAppendsDataWithoutHeaderOnSubsequentWrites() throws IOException { reporter.report("second1", "second2", "second3"); List lines = Files.readAllLines(reporter.getFilePath()); + assertEquals(3, lines.size()); assertEquals(reporter.getHeader(), lines.get(0)); assertEquals("first1\tfirst2\tfirst3", lines.get(1)); assertEquals("second1\tsecond2\tsecond3", lines.get(2)); } - @Test - void testWriteHeaderCreatesFile() throws IOException { - assertFalse(Files.exists(reporter.getFilePath())); - reporter.writeHeader(); - assertTrue(Files.exists(reporter.getFilePath())); - } - - @Test - void testWriteHeaderWritesCorrectContent() throws IOException { - reporter.writeHeader(); - List lines = Files.readAllLines(reporter.getFilePath()); - assertEquals(1, lines.size()); - assertEquals(reporter.getHeader(), lines.get(0)); - } - @Test void testReportWithEmptyValues() throws IOException { reporter.report(""); List lines = Files.readAllLines(reporter.getFilePath()); + assertEquals(2, lines.size()); + assertEquals(reporter.getHeader(), lines.get(0)); assertEquals("", lines.get(1).trim()); } @@ -108,4 +104,23 @@ void testReportWithEmptyValues() throws IOException { void testReportWithNullValues() { assertThrows(NullPointerException.class, () -> reporter.report((String[]) null)); } + + @Test + void testWriteFooterIfRowsExist_noRows_footerNotWritten() throws IOException { + reporter.writeFooterIfInitialized(); + + assertFalse(Files.exists(reporter.getFilePath())); + } + + @Test + void testWriteFooterIfRowsExist_afterRows_footerWritten() throws IOException { + reporter.report("value1", "value2", "value3"); + + reporter.writeFooterIfInitialized(); + + List lines = Files.readAllLines(reporter.getFilePath()); + + assertEquals(5, lines.size()); + assertEquals(reporter.getFooter(), lines.get(lines.size() - 1)); + } } \ No newline at end of file