From db2614036d9bd3e3805d3407bdc54400ad1bb80b Mon Sep 17 00:00:00 2001 From: Jose Duarte Date: Tue, 1 Sep 2026 18:08:26 -0700 Subject: [PATCH 1/3] Adding ext-id regexes --- .../common/constants/IdentifierRegex.java | 23 ++++- .../common/constants/IdentifierRegexTest.java | 92 ++++++++++++++++++- 2 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/rcsb/common/constants/IdentifierRegex.java b/src/main/java/org/rcsb/common/constants/IdentifierRegex.java index a0f15ff..6214618 100644 --- a/src/main/java/org/rcsb/common/constants/IdentifierRegex.java +++ b/src/main/java/org/rcsb/common/constants/IdentifierRegex.java @@ -4,14 +4,27 @@ public class IdentifierRegex { - public static final Pattern PDB_ID_REGEX = Pattern.compile("^\\d\\w\\w\\w$"); + private static final String ALPHANUM_VALUE = "[a-zA-Z0-9]"; - private static final String PDB_ID_WITH_SUFFIX_REGEX_VALUE = String.format( - "^\\d\\w\\w\\w(?:%s\\w+(?:%s\\w+)?|%s\\w+|%s\\w+)?$", + private static final String PDB_ID_REGEX_VALUE = "\\d" + ALPHANUM_VALUE + "{3}"; + + private static final String EXT_PDB_ID_REGEX_VALUE = "pdb_[a-z0-9]{8}"; + + private static final String SUFFIX_REGEX_VALUE = String.format( + "(?:%1$s%5$s+(?:%2$s%5$s+)?|%3$s%5$s+|%4$s%5$s+)?", Pattern.quote(IdentifierSeparator.ASSEMBLY_SEPARATOR), Pattern.quote(IdentifierSeparator.INTERFACE_SEPARATOR), Pattern.quote(IdentifierSeparator.ENTITY_SEPARATOR), - Pattern.quote(IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR)); + Pattern.quote(IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR), + ALPHANUM_VALUE); + + public static final Pattern PDB_ID_REGEX = Pattern.compile("^" + PDB_ID_REGEX_VALUE + "$"); + + public static final Pattern EXT_PDB_ID_REGEX = Pattern.compile("^" + EXT_PDB_ID_REGEX_VALUE + "$"); + + public static final Pattern PDB_ID_WITH_SUFFIX_REGEX = + Pattern.compile("^" + PDB_ID_REGEX_VALUE + SUFFIX_REGEX_VALUE + "$"); - public static final Pattern PDB_ID_WITH_SUFFIX_REGEX = Pattern.compile(PDB_ID_WITH_SUFFIX_REGEX_VALUE); + public static final Pattern EXT_PDB_ID_WITH_SUFFIX_REGEX = + Pattern.compile("^" + EXT_PDB_ID_REGEX_VALUE + SUFFIX_REGEX_VALUE + "$"); } diff --git a/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java b/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java index 468670e..bec9906 100644 --- a/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java +++ b/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java @@ -3,15 +3,53 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.stream.Stream; class IdentifierRegexTest { + @ParameterizedTest(name = "matches PDB ID: {0}") + @MethodSource("validPdbIds") + void matchesPdbId(String pdbId) { + assertTrue(IdentifierRegex.PDB_ID_REGEX.matcher(pdbId).matches()); + } + + private static Stream validPdbIds() { + return Stream.of("1abc", "1ABC", "1AbC", "1234"); + } + + @ParameterizedTest(name = "does not match PDB ID: {0}") + @MethodSource("invalidPdbIds") + void doesNotMatchInvalidPdbId(String pdbId) { + assertFalse(IdentifierRegex.PDB_ID_REGEX.matcher(pdbId).matches()); + } + + private static Stream invalidPdbIds() { + return Stream.of("1ab_", "1ab", "1abcd", "abcd", "1ab-"); + } + @Test - void matchesPdbId() { - assertTrue(IdentifierRegex.PDB_ID_REGEX.matcher("1abc").matches()); + void matchesExtendedPdbId() { + assertTrue(IdentifierRegex.EXT_PDB_ID_REGEX.matcher("pdb_00001abc").matches()); + } + + @ParameterizedTest(name = "does not match extended PDB ID: {0}") + @MethodSource("invalidExtendedPdbIds") + void doesNotMatchInvalidExtendedPdbId(String extPdbId) { + assertFalse(IdentifierRegex.EXT_PDB_ID_REGEX.matcher(extPdbId).matches()); + } + + private static Stream invalidExtendedPdbIds() { + return Stream.of( + "PDB_00001ABC", + "pdb_00001AbC", + "pdb_00001ab", + "pdb_00001abcd", + "1abc", + "pdb_00001ab-" + ); } @ParameterizedTest(name = "matches PDB ID with optional suffix: {0}") @@ -20,13 +58,61 @@ void matchesPdbIdOptionallyWithValidSuffix(String pdbId) { assertTrue(IdentifierRegex.PDB_ID_WITH_SUFFIX_REGEX.matcher(pdbId).matches()); } + @ParameterizedTest(name = "matches extended PDB ID with optional suffix: {0}") + @MethodSource("validExtPdbIdsWithOptionalSuffixes") + void matchesExtPdbIdOptionallyWithValidSuffix(String extPdbId) { + assertTrue(IdentifierRegex.EXT_PDB_ID_WITH_SUFFIX_REGEX.matcher(extPdbId).matches()); + } + + private static Stream validExtPdbIdsWithOptionalSuffixes() { + return Stream.of( + "pdb_00001abc", + "pdb_00001abc" + IdentifierSeparator.ENTITY_SEPARATOR + "2", + "pdb_00001abc" + IdentifierSeparator.ASSEMBLY_SEPARATOR + "1", + "pdb_00001abc" + IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR + "A", + "pdb_00001abc-1" + IdentifierSeparator.INTERFACE_SEPARATOR + "1", + "pdb_00001abc" + IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR + "AA" + ); + } + + @ParameterizedTest(name = "does not match extended PDB ID with suffix: {0}") + @MethodSource("invalidExtPdbIdsWithSuffixes") + void doesNotMatchInvalidExtPdbIdWithSuffix(String extPdbId) { + assertFalse(IdentifierRegex.EXT_PDB_ID_WITH_SUFFIX_REGEX.matcher(extPdbId).matches()); + } + + private static Stream invalidExtPdbIdsWithSuffixes() { + return Stream.of( + "PDB_00001ABC" + IdentifierSeparator.ENTITY_SEPARATOR + "2", + "pdb_00001ab" + IdentifierSeparator.ENTITY_SEPARATOR + "2", + "pdb_00001abc" + IdentifierSeparator.ENTITY_SEPARATOR, + "1abc" + IdentifierSeparator.ENTITY_SEPARATOR + "2", + "pdb_00001abc" + IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR + "A_1" + ); + } + + @ParameterizedTest(name = "does not match PDB ID with suffix: {0}") + @MethodSource("invalidPdbIdsWithSuffixes") + void doesNotMatchInvalidPdbIdWithSuffix(String pdbId) { + assertFalse(IdentifierRegex.PDB_ID_WITH_SUFFIX_REGEX.matcher(pdbId).matches()); + } + + private static Stream invalidPdbIdsWithSuffixes() { + return Stream.of( + "1abc" + IdentifierSeparator.ASSEMBLY_SEPARATOR + "1_1", + "1abc" + IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR + "A_", + "1abc" + IdentifierSeparator.ASSEMBLY_SEPARATOR + ); + } + private static Stream validPdbIdsWithOptionalSuffixes() { return Stream.of( "1abc", "1abc" + IdentifierSeparator.ENTITY_SEPARATOR + "2", "1abc" + IdentifierSeparator.ASSEMBLY_SEPARATOR + "1", "1abc" + IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR + "A", - "1abc-1" + IdentifierSeparator.INTERFACE_SEPARATOR + "1" + "1abc-1" + IdentifierSeparator.INTERFACE_SEPARATOR + "1", + "1ABC" + IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR + "AA" ); } } \ No newline at end of file From 10ce52970ddc7bb8d202e42c27e151daaf25b679 Mon Sep 17 00:00:00 2001 From: Jose Duarte Date: Tue, 1 Sep 2026 18:09:15 -0700 Subject: [PATCH 2/3] Updating version to bump minor for next release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 99de12c..baeaa03 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.rcsb rcsb-common - 3.1.1-SNAPSHOT + 3.2.0-SNAPSHOT jar rcsb-common Common constants for RCSB projects. From 71f39ffe4b4f0ebc408dd7c84f5a330a1cd30b1a Mon Sep 17 00:00:00 2001 From: Jose Duarte Date: Tue, 1 Sep 2026 21:12:48 -0700 Subject: [PATCH 3/3] Adding API on top of regexes --- .../common/constants/IdentifierRegex.java | 53 +++++++++++++++++++ .../common/constants/IdentifierRegexTest.java | 38 ++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/rcsb/common/constants/IdentifierRegex.java b/src/main/java/org/rcsb/common/constants/IdentifierRegex.java index 6214618..4e5f372 100644 --- a/src/main/java/org/rcsb/common/constants/IdentifierRegex.java +++ b/src/main/java/org/rcsb/common/constants/IdentifierRegex.java @@ -2,6 +2,13 @@ import java.util.regex.Pattern; +/** + * Regular expressions for PDB identifiers, plus convenience methods to test strings against them. + * + *

Two identifier flavours are supported: the legacy 4-character id (e.g. {@code 1abc}) and the + * extended id (e.g. {@code pdb_00001abc}). Both may optionally carry a suffix built from the + * separators in {@link IdentifierSeparator}. + */ public class IdentifierRegex { private static final String ALPHANUM_VALUE = "[a-zA-Z0-9]"; @@ -18,13 +25,59 @@ public class IdentifierRegex { Pattern.quote(IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR), ALPHANUM_VALUE); + /** A legacy PDB id: a digit followed by 3 alphanumeric characters, e.g. {@code 1abc}. */ public static final Pattern PDB_ID_REGEX = Pattern.compile("^" + PDB_ID_REGEX_VALUE + "$"); + /** An extended PDB id: {@code pdb_} followed by 8 lowercase alphanumeric characters, e.g. {@code pdb_00001abc}. */ public static final Pattern EXT_PDB_ID_REGEX = Pattern.compile("^" + EXT_PDB_ID_REGEX_VALUE + "$"); + /** A legacy PDB id with an optional assembly, interface, entity or entity instance suffix. */ public static final Pattern PDB_ID_WITH_SUFFIX_REGEX = Pattern.compile("^" + PDB_ID_REGEX_VALUE + SUFFIX_REGEX_VALUE + "$"); + /** An extended PDB id with an optional assembly, interface, entity or entity instance suffix. */ public static final Pattern EXT_PDB_ID_WITH_SUFFIX_REGEX = Pattern.compile("^" + EXT_PDB_ID_REGEX_VALUE + SUFFIX_REGEX_VALUE + "$"); + + private IdentifierRegex() {} + + /** + * Tells whether the given string is a legacy PDB id: a digit followed by 3 alphanumeric + * characters, in either case (e.g. {@code 1abc}, {@code 1ABC}). + * + *

Suffixes are not accepted: use {@link #PDB_ID_WITH_SUFFIX_REGEX} for those. + * + * @param id the string to test, can be null + * @return true if the string is a legacy PDB id, false otherwise or if id is null + * @since 3.2.0 + */ + public static boolean isLegacyPdbId(String id) { + return id != null && PDB_ID_REGEX.matcher(id).matches(); + } + + /** + * Tells whether the given string is an extended PDB id: the prefix {@code pdb_} followed by 8 + * lowercase alphanumeric characters (e.g. {@code pdb_00001abc}). Uppercase is not accepted. + * + *

Suffixes are not accepted: use {@link #EXT_PDB_ID_WITH_SUFFIX_REGEX} for those. + * + * @param id the string to test, can be null + * @return true if the string is an extended PDB id, false otherwise or if id is null + * @since 3.2.0 + */ + public static boolean isExtPdbId(String id) { + return id != null && EXT_PDB_ID_REGEX.matcher(id).matches(); + } + + /** + * Tells whether the given string is a PDB id in either of the supported flavours, i.e. whether + * {@link #isLegacyPdbId(String)} or {@link #isExtPdbId(String)} holds. + * + * @param id the string to test, can be null + * @return true if the string is a legacy or extended PDB id, false otherwise or if id is null + * @since 3.2.0 + */ + public static boolean isAnyPdbId(String id) { + return isLegacyPdbId(id) || isExtPdbId(id); + } } diff --git a/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java b/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java index bec9906..1791a40 100644 --- a/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java +++ b/src/test/java/org/rcsb/common/constants/IdentifierRegexTest.java @@ -3,6 +3,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -115,4 +117,38 @@ private static Stream validPdbIdsWithOptionalSuffixes() { "1ABC" + IdentifierSeparator.ENTITY_INSTANCE_SEPARATOR + "AA" ); } -} \ No newline at end of file + + @ParameterizedTest(name = "isLegacyPdbId({0})") + @MethodSource("validPdbIds") + void isLegacyPdbIdTrueForLegacyIds(String id) { + assertTrue(IdentifierRegex.isLegacyPdbId(id)); + assertTrue(IdentifierRegex.isAnyPdbId(id)); + assertFalse(IdentifierRegex.isExtPdbId(id)); + } + + @ParameterizedTest(name = "isExtPdbId({0})") + @ValueSource(strings = {"pdb_00001abc", "pdb_12345678", "pdb_abcdefgh"}) + void isExtPdbIdTrueForExtendedIds(String id) { + assertTrue(IdentifierRegex.isExtPdbId(id)); + assertTrue(IdentifierRegex.isAnyPdbId(id)); + assertFalse(IdentifierRegex.isLegacyPdbId(id)); + } + + @ParameterizedTest(name = "no flavour matches: {0}") + @NullSource + @ValueSource(strings = { + "", + "1ab", + "1abcd", + "PDB_00001ABC", + "pdb_00001AbC", + "pdb_00001ab", + "1abc" + "_" + "2", + "pdb_00001abc" + "_" + "2" + }) + void noFlavourMatchesInvalidOrSuffixedIds(String id) { + assertFalse(IdentifierRegex.isLegacyPdbId(id)); + assertFalse(IdentifierRegex.isExtPdbId(id)); + assertFalse(IdentifierRegex.isAnyPdbId(id)); + } +}