From 36f29d40ef8e3929b58b8f51c663db1250665263 Mon Sep 17 00:00:00 2001 From: Thomas Grininger Date: Sat, 22 Feb 2025 16:29:10 +0100 Subject: [PATCH] feat: Adding possibility to invert vers ref: #171 --- .../github/nscuro/versatile/Constraint.java | 16 ++++ .../java/io/github/nscuro/versatile/Vers.java | 13 ++++ .../io/github/nscuro/versatile/VersTest.java | 73 +++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/versatile-core/src/main/java/io/github/nscuro/versatile/Constraint.java b/versatile-core/src/main/java/io/github/nscuro/versatile/Constraint.java index 9764be5..ad38ad9 100644 --- a/versatile-core/src/main/java/io/github/nscuro/versatile/Constraint.java +++ b/versatile-core/src/main/java/io/github/nscuro/versatile/Constraint.java @@ -95,6 +95,22 @@ boolean matches(final Version version) { }; } + /** + * Inverts the comparator of the constraint e.g. < 1.3 becomes >= 1.3 + * @return a new inverted constraint and null if current comparator is a wildcard: * + */ + Constraint invert(){ + return switch (comparator) { + case LESS_THAN -> new Constraint(scheme, Comparator.GREATER_THAN_OR_EQUAL, version); + case LESS_THAN_OR_EQUAL -> new Constraint(scheme, Comparator.GREATER_THAN, version); + case GREATER_THAN_OR_EQUAL -> new Constraint(scheme, Comparator.LESS_THAN, version); + case GREATER_THAN -> new Constraint(scheme, Comparator.LESS_THAN_OR_EQUAL, version); + case EQUAL -> new Constraint(scheme, Comparator.NOT_EQUAL, version); + case NOT_EQUAL -> new Constraint(scheme, Comparator.EQUAL, version); + case WILDCARD -> null; + }; + } + private static String maybeUrlDecode(final String version) { if (version.contains("%")) { return URLDecoder.decode(version, StandardCharsets.UTF_8); diff --git a/versatile-core/src/main/java/io/github/nscuro/versatile/Vers.java b/versatile-core/src/main/java/io/github/nscuro/versatile/Vers.java index 698b27a..f9a6b8e 100644 --- a/versatile-core/src/main/java/io/github/nscuro/versatile/Vers.java +++ b/versatile-core/src/main/java/io/github/nscuro/versatile/Vers.java @@ -499,6 +499,19 @@ public boolean overlapsWith(Vers vers) { return false; } + /** + * Inverts a given vers expression and returns a new simplified vers + * + * @throws VersException if the vers is a wildcard + */ + public Vers invert() { + if (isWildcard()) { + throw new VersException("Can not invert wildcard vers"); + } + var inverted = constraints.stream().map(Constraint::invert).toList(); + return new Vers(this.scheme(), inverted).simplify(); + } + @Override public String toString() { final String schemeStr = scheme().toLowerCase(); diff --git a/versatile-core/src/test/java/io/github/nscuro/versatile/VersTest.java b/versatile-core/src/test/java/io/github/nscuro/versatile/VersTest.java index 1f27174..eaf9621 100644 --- a/versatile-core/src/test/java/io/github/nscuro/versatile/VersTest.java +++ b/versatile-core/src/test/java/io/github/nscuro/versatile/VersTest.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.params.provider.Arguments.arguments; class VersTest { @@ -221,4 +222,76 @@ void testHasOverlap(String version1, String version2, boolean expected) { assertThat(v1.overlapsWith(v2)).isEqualTo(expected); assertThat(v2.overlapsWith(v1)).isEqualTo(expected); } + + + @ParameterizedTest + @CsvSource({ + "'vers:generic/1.2.3', 'vers:generic/!=1.2.3'", + "'vers:generic/>=1.2.0|<2.0.0', 'vers:generic/<1.2.0|>=2.0.0'", + "'vers:generic/<1.2.0', 'vers:generic/>=1.2.0'", + "'vers:generic/>=1.0.0|<3.0.0', 'vers:generic/<1.0.0|>=3.0.0'", + "'vers:generic/1.0.0|2.0.0', 'vers:generic/!=1.0.0|!=2.0.0'", + "'vers:generic/<2.0.0', 'vers:generic/>=2.0.0'", + "'vers:generic/>=1.0.0|<3.0.0', 'vers:generic/<1.0.0|>=3.0.0'", + "'vers:generic/1.0.0|1.1.0', 'vers:generic/!=1.0.0|!=1.1.0'", + "'vers:generic/<1.0.0|>=3.0.0', 'vers:generic/>=1.0.0|<3.0.0'", + "'vers:generic/>=1.0.0|<2.5.0|>=3.0.0|<4.0.0', 'vers:generic/<1.0.0|>=2.5.0|<3.0.0|>=4.0.0'", + "'vers:generic/<1.0.0|>=2.0.0|<2.5.0', 'vers:generic/>=1.0.0|<2.0.0|>=2.5.0'", + "'vers:generic/<3.0.0|>=1.0.0', 'vers:generic/<1.0.0|>=3.0.0'", + "'vers:generic/1.0.0|1.2.0|1.4.0', 'vers:generic/!=1.0.0|!=1.2.0|!=1.4.0'", + "'vers:generic/>=1.0.0|<5.0.0', 'vers:generic/<1.0.0|>=5.0.0'", + "'vers:generic/>=1.0.0|<2.0.0', 'vers:generic/<1.0.0|>=2.0.0'", + "'vers:generic/>1.2.3', 'vers:generic/<=1.2.3'", + "'vers:generic/>1.2.3|<1.2.9', 'vers:generic/<=1.2.3|>=1.2.9'", + "'vers:generic/>1.2.3|<1.3.1', 'vers:generic/<=1.2.3|>=1.3.1'", + "'vers:generic/>=1.0.0|<2.0.0|>=2.5.0|<3.5.0', 'vers:generic/<1.0.0|>=2.0.0|<2.5.0|>=3.5.0'", + "'vers:generic/<1.5.0|>=2.0.0|!=2.7.0|<3.2.0', 'vers:generic/>=1.5.0|<2.0.0|2.7.0|>=3.2.0'", + "'vers:generic/>=1.0.0|!=1.5.0|<2.5.0|>=3.0.0|!=3.2.0', 'vers:generic/<1.0.0|1.5.0|>=2.5.0|<3.0.0|3.2.0'", + "'vers:generic/>1.2.3|!=2.0.0|<=3.5.1|3.6.2|>4.0.0|<5.0.0', 'vers:generic/<=1.2.3|2.0.0|>3.5.1|!=3.6.2|<=4.0.0|>=5.0.0'", + "'vers:generic/>=1.1.0|<2.2.0|!=2.1.0|>=3.0.0|<4.0.0', 'vers:generic/<1.1.0|>=2.2.0|<3.0.0|>=4.0.0'" + }) + void testInvert(String version, String expectedInverted) { + var v = Vers.parse(version); + var inverted = v.invert(); + assertThat(inverted.toString()).isEqualTo(expectedInverted); + } + + @ParameterizedTest + @CsvSource({ + "'vers:generic/1.2.3'", + "'vers:generic/>=1.2.0|<2.0.0'", + "'vers:generic/<1.2.0'", + "'vers:generic/>=1.0.0|<3.0.0'", + "'vers:generic/1.0.0|2.0.0'", + "'vers:generic/<2.0.0'", + "'vers:generic/>=1.0.0|<3.0.0'", + "'vers:generic/1.0.0|1.1.0'", + "'vers:generic/<1.0.0|>=3.0.0'", + "'vers:generic/>=1.0.0|<2.5.0|>=3.0.0|<4.0.0'", + "'vers:generic/<1.0.0|>=2.0.0|<2.5.0'", + "'vers:generic/>=1.0.0|<3.0.0'", + "'vers:generic/1.0.0|1.2.0|1.4.0'", + "'vers:generic/>=1.0.0|<5.0.0'", + "'vers:generic/>=1.0.0|<2.0.0'", + "'vers:generic/>1.2.3'", + "'vers:generic/>1.2.3|<1.2.9'", + "'vers:generic/>1.2.3|<1.3.1'", + "'vers:generic/>=1.0.0|<2.0.0|>=2.5.0|<3.5.0'", + "'vers:generic/<1.5.0|>=2.0.0|!=2.7.0|<3.2.0'", + "'vers:generic/>=1.0.0|!=1.5.0|<2.5.0|>=3.0.0|!=3.2.0'", + "'vers:generic/>1.2.3|!=2.0.0|<=3.5.1|>4.0.0|<5.0.0'", + "'vers:generic/>=1.1.0|<2.2.0|!=2.1.0|>=3.0.0|<4.0.0'" + }) + void testInvertedDoesNotOverlap(String version) { + var v = Vers.parse(version); + var inverted = v.invert(); + assertThat(v.overlapsWith(inverted)).isEqualTo(false); + } + + @Test + void testInvertThrowsErrorOnWildcard() { + var v = Vers.parse("vers:generic/*"); + assertThatThrownBy(v::invert) + .isInstanceOf(VersException.class); + } } \ No newline at end of file